EN: Allow exporting key of current day

This requires rotating the daily TEK when exporting
This commit is contained in:
Marvin W 2020-12-11 11:51:53 +01:00
parent 0e0ac35e51
commit 369c3d7557
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
4 changed files with 47 additions and 33 deletions

View File

@ -39,7 +39,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp" android:padding="16dp"
tools:text="Your phonre needs to use Bluetooth to securely collect and share IDs with other phones that are nearby.\n\nCorona Warn can notify you if you were exposed to someone who reported to be diagnosed positive.\n\nThe date, duration, and signal strength associated with an exposure will be shared with the app." /> tools:text="Your phone needs to use Bluetooth to securely collect and share IDs with other phones that are nearby.\n\nCorona Warn can notify you if you were exposed to someone who reported to be diagnosed positive.\n\nThe date, duration, and signal strength associated with an exposure will be shared with the app." />
<LinearLayout <LinearLayout
android:id="@+id/grant_permission_view" android:id="@+id/grant_permission_view"

View File

@ -34,11 +34,14 @@ private const val AES_BLOCK_SIZE = 16
private const val AEM_ALGORITHM = "AES/CTR/NoPadding" private const val AEM_ALGORITHM = "AES/CTR/NoPadding"
val currentIntervalNumber: Long val currentIntervalNumber: Int
get() = floor(System.currentTimeMillis() / 1000.0 / ROLLING_WINDOW_LENGTH).toLong() get() = floor(System.currentTimeMillis() / 1000.0 / ROLLING_WINDOW_LENGTH).toInt()
val currentRollingStartNumber: Long val currentDayRollingStartNumber: Int
get() = floor(currentIntervalNumber.toDouble() / ROLLING_PERIOD).toLong() * ROLLING_PERIOD get() = getDayRollingStartNumber(currentIntervalNumber)
fun getDayRollingStartNumber(intervalNumber: Int) = (floor(currentIntervalNumber.toDouble() / ROLLING_PERIOD).toLong() * ROLLING_PERIOD).toInt()
fun getPeriodInDay(intervalNumber: Int) = intervalNumber - getDayRollingStartNumber(intervalNumber)
val nextKeyMillis: Long val nextKeyMillis: Long
get() { get() {
@ -47,18 +50,16 @@ val nextKeyMillis: Long
return (currentWindowEnd - System.currentTimeMillis()).coerceAtLeast(0) return (currentWindowEnd - System.currentTimeMillis()).coerceAtLeast(0)
} }
fun TemporaryExposureKey.TemporaryExposureKeyBuilder.setCurrentRollingStartNumber(): TemporaryExposureKey.TemporaryExposureKeyBuilder = fun generateTemporaryExposureKey(intervalNumber: Int): TemporaryExposureKey.TemporaryExposureKeyBuilder = TemporaryExposureKey.TemporaryExposureKeyBuilder().apply {
setRollingStartIntervalNumber(currentRollingStartNumber.toInt())
fun TemporaryExposureKey.TemporaryExposureKeyBuilder.generate(): TemporaryExposureKey.TemporaryExposureKeyBuilder {
var keyData = ByteArray(16) var keyData = ByteArray(16)
SecureRandom().nextBytes(keyData) SecureRandom().nextBytes(keyData)
setKeyData(keyData) setKeyData(keyData)
setRollingPeriod(ROLLING_PERIOD) setRollingStartIntervalNumber(intervalNumber)
return this setRollingPeriod((ROLLING_PERIOD - getPeriodInDay(intervalNumber)))
} }
fun generateCurrentTemporaryExposureKey(): TemporaryExposureKey = TemporaryExposureKey.TemporaryExposureKeyBuilder().generate().setCurrentRollingStartNumber().build() fun generateCurrentDayTemporaryExposureKey(): TemporaryExposureKey = generateTemporaryExposureKey(currentDayRollingStartNumber).build()
fun generateIntraDayTemporaryExposureKey(intervalNumber: Int = currentIntervalNumber): TemporaryExposureKey = generateTemporaryExposureKey(intervalNumber).build()
@TargetApi(21) @TargetApi(21)
fun TemporaryExposureKey.generateRpiKey(): SecretKeySpec { fun TemporaryExposureKey.generateRpiKey(): SecretKeySpec {

View File

@ -15,14 +15,11 @@ import android.database.sqlite.SQLiteOpenHelper
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence
import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping
import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import kotlinx.coroutines.* import kotlinx.coroutines.*
import okio.ByteString import okio.ByteString
import org.json.JSONObject
import org.microg.gms.nearby.exposurenotification.Constants.TOKEN_A
import java.io.File import java.io.File
import java.lang.Runnable import java.lang.Runnable
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -111,7 +108,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
fun dailyCleanup(): Boolean = writableDatabase.run { fun dailyCleanup(): Boolean = writableDatabase.run {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val rollingStartTime = currentRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong()) val rollingStartTime = currentDayRollingStartNumber * ROLLING_WINDOW_LENGTH * 1000 - TimeUnit.DAYS.toMillis(KEEP_DAYS.toLong())
val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime)) val advertisements = delete(TABLE_ADVERTISEMENTS, "timestamp < ?", longArrayOf(rollingStartTime))
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv") Log.d(TAG, "Deleted on daily cleanup: $advertisements adv")
if (start + MAX_DELETE_TIME < System.currentTimeMillis()) return@run false if (start + MAX_DELETE_TIME < System.currentTimeMillis()) return@run false
@ -533,8 +530,9 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
} }
} }
private fun findOwnKeyAt(rollingStartNumber: Int, database: SQLiteDatabase = readableDatabase): TemporaryExposureKey? = database.run { private fun findOwnKeyAt(intervalNumber: Int, database: SQLiteDatabase = readableDatabase): TemporaryExposureKey? = database.run {
query(TABLE_TEK, arrayOf("keyData", "rollingStartNumber", "rollingPeriod"), "rollingStartNumber = ?", arrayOf(rollingStartNumber.toString()), null, null, null).use { cursor -> val dayRollingStartNumber = getDayRollingStartNumber(intervalNumber)
query(TABLE_TEK, arrayOf("keyData", "rollingStartNumber", "rollingPeriod"), "rollingStartNumber >= ? AND (rollingStartNumber + rollingPeriod) < ?", arrayOf(dayRollingStartNumber.toString(), intervalNumber.toString()), null, null, "rollingStartNumber DESC").use { cursor ->
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
TemporaryExposureKey.TemporaryExposureKeyBuilder() TemporaryExposureKey.TemporaryExposureKeyBuilder()
.setKeyData(cursor.getBlob(0)) .setKeyData(cursor.getBlob(0))
@ -602,18 +600,33 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
} }
} }
val allKeys: List<TemporaryExposureKey> = readableDatabase.run { fun exportKeys(database: SQLiteDatabase = writableDatabase): List<TemporaryExposureKey> = database.run {
val startRollingNumber = (currentRollingStartNumber - 14 * ROLLING_PERIOD) database.beginTransactionNonExclusive()
query(TABLE_TEK, arrayOf("keyData", "rollingStartNumber", "rollingPeriod"), "rollingStartNumber >= ? AND rollingStartNumber < ?", arrayOf(startRollingNumber.toString(), currentIntervalNumber.toString()), null, null, null).use { cursor -> try {
val list = arrayListOf<TemporaryExposureKey>() val intervalNumber = currentIntervalNumber
while (cursor.moveToNext()) { val key = findOwnKeyAt(intervalNumber, database)
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder() if (key != null && intervalNumber != key.rollingStartIntervalNumber) {
.setKeyData(cursor.getBlob(0)) // Rotate key
.setRollingStartIntervalNumber(cursor.getLong(1).toInt()) update(TABLE_TEK, ContentValues().apply {
.setRollingPeriod(cursor.getLong(2).toInt()) put("rollingPeriod", intervalNumber - key.rollingStartIntervalNumber)
.build()) }, "rollingStartNumber = ?", arrayOf(key.rollingStartIntervalNumber.toString()))
storeOwnKey(generateIntraDayTemporaryExposureKey(intervalNumber), database)
} }
list database.setTransactionSuccessful()
val startRollingNumber = (getDayRollingStartNumber(intervalNumber) - 14 * ROLLING_PERIOD)
query(TABLE_TEK, arrayOf("keyData", "rollingStartNumber", "rollingPeriod"), "rollingStartNumber >= ? AND (rollingStartNumber + rollingPeriod) <= ?", arrayOf(startRollingNumber.toString(), intervalNumber.toString()), null, null, null).use { cursor ->
val list = arrayListOf<TemporaryExposureKey>()
while (cursor.moveToNext()) {
list.add(TemporaryExposureKey.TemporaryExposureKeyBuilder()
.setKeyData(cursor.getBlob(0))
.setRollingStartIntervalNumber(cursor.getLong(1).toInt())
.setRollingPeriod(cursor.getLong(2).toInt())
.build())
}
list
}
} finally {
database.endTransaction()
} }
} }
@ -733,9 +746,9 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
private fun ensureTemporaryExposureKey(): TemporaryExposureKey = writableDatabase.let { database -> private fun ensureTemporaryExposureKey(): TemporaryExposureKey = writableDatabase.let { database ->
database.beginTransactionNonExclusive() database.beginTransactionNonExclusive()
try { try {
var key = findOwnKeyAt(currentRollingStartNumber.toInt(), database) var key = findOwnKeyAt(currentIntervalNumber.toInt(), database)
if (key == null) { if (key == null) {
key = generateCurrentTemporaryExposureKey() key = generateCurrentDayTemporaryExposureKey()
storeOwnKey(key, database) storeOwnKey(key, database)
} }
database.setTransactionSuccessful() database.setTransactionSuccessful()
@ -747,7 +760,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
val currentRpiId: UUID? val currentRpiId: UUID?
get() { get() {
val key = findOwnKeyAt(currentRollingStartNumber.toInt()) ?: return null val key = findOwnKeyAt(currentIntervalNumber.toInt()) ?: return null
val buffer = ByteBuffer.wrap(key.generateRpiId(currentIntervalNumber.toInt())) val buffer = ByteBuffer.wrap(key.generateRpiId(currentIntervalNumber.toInt()))
return UUID(buffer.long, buffer.long) return UUID(buffer.long, buffer.long)
} }

View File

@ -146,7 +146,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
val status = confirmPermission(CONFIRM_ACTION_KEYS) val status = confirmPermission(CONFIRM_ACTION_KEYS)
val response = when { val response = when {
status.isSuccess -> ExposureDatabase.with(context) { database -> status.isSuccess -> ExposureDatabase.with(context) { database ->
database.allKeys database.exportKeys()
} }
else -> emptyList() else -> emptyList()
} }