Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
Add check-Ins to submission payload (EXPOSUREAPP-5656) (#2602)
Browse files Browse the repository at this point in the history
* Add check-ins to submission payload

* Use new classes -  map checkin fields

* Rename to transformer

* Satisfy tests

* lint

* Use new models

* Add space to satisfy ktlint the magnificent

* Create DefaultCheckInsTransformerTest.kt

* Clear check-ins after submission

* Fix tests

* Update SubmissionTaskTest.kt

Co-authored-by: Lukas Lechner <lukas.lechner@sap.com>
  • Loading branch information
mtwalli and LukasLechnerDev authored Mar 17, 2021
1 parent 0bc79cb commit fc8ece0
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ import de.rki.coronawarnapp.eventregistration.checkins.qrcode.DefaultQRCodeVerif
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier
import de.rki.coronawarnapp.eventregistration.storage.repo.DefaultTraceLocationRepository
import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
import de.rki.coronawarnapp.eventregistration.checkins.CheckInsTransformer
import de.rki.coronawarnapp.eventregistration.checkins.DefaultCheckInsTransformer

@Module
abstract class EventRegistrationModule {

@Binds
abstract fun checkInsTransformer(transformer: DefaultCheckInsTransformer): CheckInsTransformer

@Binds
abstract fun qrCodeVerifier(qrCodeVerifier: DefaultQRCodeVerifier): QRCodeVerifier

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class CheckInRepository @Inject constructor(
checkInDao.update(checkIn.toEntity())
}
}

fun clear() {
appScope.launch {
checkInDao.deleteAll()
}
}
}

private fun TraceLocationCheckInEntity.toCheckIn() = CheckIn(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.rki.coronawarnapp.eventregistration.checkins

import de.rki.coronawarnapp.server.protocols.internal.pt.CheckInOuterClass

interface CheckInsTransformer {
fun transform(checkIns: List<CheckIn>): List<CheckInOuterClass.CheckIn>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package de.rki.coronawarnapp.eventregistration.checkins

import com.google.protobuf.ByteString
import de.rki.coronawarnapp.server.protocols.internal.pt.CheckInOuterClass
import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds
import javax.inject.Inject

class DefaultCheckInsTransformer @Inject constructor() :
CheckInsTransformer {
override fun transform(checkIns: List<CheckIn>): List<CheckInOuterClass.CheckIn> {
return checkIns.map { checkIn ->
val traceLocation = TraceLocationOuterClass.TraceLocation.newBuilder()
.setGuid(checkIn.guid)
.setVersion(checkIn.version)
.setType(TraceLocationOuterClass.TraceLocationType.forNumber(checkIn.type))
.setDescription(checkIn.description)
.setAddress(checkIn.address)
.setStartTimestamp(checkIn.traceLocationStart?.seconds ?: 0L)
.setEndTimestamp(checkIn.traceLocationEnd?.seconds ?: 0L)
.setDefaultCheckInLengthInMinutes(checkIn.defaultCheckInLengthInMinutes ?: 0)
.build()

val signedTraceLocation = TraceLocationOuterClass.SignedTraceLocation.newBuilder()
.setLocation(traceLocation)
.setSignature(ByteString.copyFrom(checkIn.signature.toByteArray()))
.build()

CheckInOuterClass.CheckIn.newBuilder()
.setSignedLocation(signedTraceLocation)
.setStartIntervalNumber(checkIn.checkInStart.seconds.toInt())
.setEndIntervalNumber(checkIn.checkInEnd?.seconds?.toInt() ?: 0)
// TODO .setTransmissionRiskLevel()
.build()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ class DefaultPlaybook @Inject constructor(
authCode = authCode,
keyList = data.temporaryExposureKeys,
consentToFederation = data.consentToFederation,
visitedCountries = data.visitedCountries
visitedCountries = data.visitedCountries,
checkIns = data.checkIns
)
submissionServer.submitKeysToServer(serverSubmissionData)
coroutineScope.launch { followUpPlaybooks() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
package de.rki.coronawarnapp.playbook

import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass
import de.rki.coronawarnapp.server.protocols.internal.pt.CheckInOuterClass
import de.rki.coronawarnapp.util.formatter.TestResult
import de.rki.coronawarnapp.verification.server.VerificationKeyType

/**
* The concept of Plausible Deniability aims to hide the existence of a positive test result by always using a defined “playbook pattern” of requests to the Verification Server and CWA Backend so it is impossible for an attacker to identify which communication was done.
* The “playbook pattern” represents a well-defined communication pattern consisting of dummy requests and real requests.
* To hide that a real request was done, the device does multiple of these requests over a longer period of time according to the previously defined communication pattern statistically similar to all apps so it is not possible to infer by observing the traffic if the requests under concern are real or the fake ones.
* The concept of Plausible Deniability aims to hide the existence of a positive test result by always using a defined
* “playbook pattern” of requests to the Verification Server and CWA Backend so it is impossible for an attacker to
* identify which communication was done.
*
* The “playbook pattern” represents a well-defined communication pattern consisting of fake requests and real
* requests.
*
* To hide that a real request was done, the device does multiple of these requests over a longer period of time
* according to the previously defined communication pattern statistically similar to all apps so it is not possible to
* infer by observing the traffic if the requests under concern are real or the fake ones.
*/
interface Playbook {

/**
* @return pair of Registration token [String] & [TestResult]
*/
suspend fun initialRegistration(
key: String,
keyType: VerificationKeyType
): Pair<String, TestResult> /* registration token & test result*/
): Pair<String, TestResult>

suspend fun testResult(registrationToken: String): TestResult

Expand All @@ -26,6 +37,7 @@ interface Playbook {
val registrationToken: String,
val temporaryExposureKeys: List<TemporaryExposureKeyExportOuterClass.TemporaryExposureKey>,
val consentToFederation: Boolean,
val visitedCountries: List<String>
val visitedCountries: List<String>,
val checkIns: List<CheckInOuterClass.CheckIn>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.google.protobuf.ByteString
import dagger.Lazy
import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass.TemporaryExposureKey
import de.rki.coronawarnapp.server.protocols.internal.SubmissionPayloadOuterClass.SubmissionPayload
import de.rki.coronawarnapp.server.protocols.internal.pt.CheckInOuterClass

import de.rki.coronawarnapp.util.PaddingTool.requestPadding
import kotlinx.coroutines.Dispatchers
Expand All @@ -25,7 +26,8 @@ class SubmissionServer @Inject constructor(
val authCode: String,
val keyList: List<TemporaryExposureKey>,
val consentToFederation: Boolean,
val visitedCountries: List<String>
val visitedCountries: List<String>,
val checkIns: List<CheckInOuterClass.CheckIn>
)

suspend fun submitKeysToServer(
Expand All @@ -48,6 +50,7 @@ class SubmissionServer @Inject constructor(
.setRequestPadding(ByteString.copyFromUtf8(fakeKeyPadding))
.setConsentToFederation(data.consentToFederation)
.addAllVisitedCountries(data.visitedCountries)
.addAllCheckIns(data.checkIns)
.build()

api.submitKeys(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package de.rki.coronawarnapp.submission.task
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository
import de.rki.coronawarnapp.eventregistration.checkins.CheckInsTransformer
import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
import de.rki.coronawarnapp.notification.ShareTestResultNotificationService
import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService
Expand Down Expand Up @@ -37,6 +39,8 @@ class SubmissionTask @Inject constructor(
private val timeStamper: TimeStamper,
private val shareTestResultNotificationService: ShareTestResultNotificationService,
private val testResultAvailableNotificationService: TestResultAvailableNotificationService,
private val checkInsRepository: CheckInRepository,
private val checkInsTransformer: CheckInsTransformer,
private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
) : Task<DefaultProgress, SubmissionTask.Result> {

Expand Down Expand Up @@ -137,11 +141,17 @@ class SubmissionTask @Inject constructor(
)
Timber.tag(TAG).d("Transformed keys with symptoms %s from %s to %s", symptoms, keys, transformedKeys)

val checkIns = checkInsRepository.allCheckIns.first()
val transformedCheckIns = checkInsTransformer.transform(checkIns)

Timber.tag(TAG).d("Transformed CheckIns from: %s to: %s", checkIns, transformedCheckIns)

val submissionData = Playbook.SubmissionData(
registrationToken,
transformedKeys,
true,
getSupportedCountries()
registrationToken = registrationToken,
temporaryExposureKeys = transformedKeys,
consentToFederation = true,
visitedCountries = getSupportedCountries(),
checkIns = transformedCheckIns
)

checkCancel()
Expand All @@ -154,6 +164,7 @@ class SubmissionTask @Inject constructor(

Timber.tag(TAG).d("Submission successful, deleting submission data.")
tekHistoryStorage.clear()
checkInsRepository.clear()
submissionSettings.symptoms.update { null }

autoSubmission.updateMode(AutoSubmission.Mode.DISABLED)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package de.rki.coronawarnapp.eventregistration.checkins

import com.google.protobuf.ByteString
import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
import io.kotest.matchers.shouldBe
import org.joda.time.Instant
import org.junit.jupiter.api.Test
import testhelpers.BaseTest

class DefaultCheckInsTransformerTest : BaseTest() {

private val checkInTransformer = DefaultCheckInsTransformer()

@Test
fun `transform check-ins`() {
val checkIn1 = CheckIn(
id = 0,
guid = "3055331c-2306-43f3-9742-6d8fab54e848",
version = 1,
type = 2,
description = "description1",
address = "address1",
traceLocationStart = Instant.ofEpochMilli(2687955 * 1_000L),
traceLocationEnd = Instant.ofEpochMilli(2687991 * 1_000L),
defaultCheckInLengthInMinutes = 10,
signature = "signature1",
checkInStart = Instant.ofEpochMilli(2687955 * 1_000L),
checkInEnd = Instant.ofEpochMilli(2687991 * 1_000L),
targetCheckInEnd = null,
createJournalEntry = true
)

val checkIn2 = CheckIn(
id = 1,
guid = "fca84b37-61c0-4a7c-b2f8-825cadd506cf",
version = 1,
type = 1,
description = "description2",
address = "address2",
traceLocationStart = null,
traceLocationEnd = null,
defaultCheckInLengthInMinutes = 20,
signature = "signature2",
checkInStart = Instant.ofEpochMilli(2687955 * 1_000L),
checkInEnd = null,
targetCheckInEnd = null,
createJournalEntry = false
)

val outCheckIns = checkInTransformer.transform(
listOf(
checkIn1,
checkIn2
)
)
outCheckIns.size shouldBe 2

outCheckIns[0].apply {
signedLocation.apply {
location.apply {
guid shouldBe "3055331c-2306-43f3-9742-6d8fab54e848"
version shouldBe 1
type shouldBe TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_OTHER
description shouldBe "description1"
address shouldBe "address1"
startTimestamp shouldBe 2687955
endTimestamp shouldBe 2687991
defaultCheckInLengthInMinutes shouldBe 10
}
signature shouldBe ByteString.copyFrom("signature1".toByteArray())
}
startIntervalNumber shouldBe 2687955
endIntervalNumber shouldBe 2687991
// TODO transmissionRiskLevel shouldBe
}

outCheckIns[1].apply {
signedLocation.apply {
location.apply {
guid shouldBe "fca84b37-61c0-4a7c-b2f8-825cadd506cf"
version shouldBe 1
type shouldBe TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_PERMANENT_OTHER
description shouldBe "description2"
address shouldBe "address2"
startTimestamp shouldBe 0
endTimestamp shouldBe 0
defaultCheckInLengthInMinutes shouldBe 20
}
signature shouldBe ByteString.copyFrom("signature2".toByteArray())
}
startIntervalNumber shouldBe 2687955
endIntervalNumber shouldBe 0
// TODO transmissionRiskLevel shouldBe
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ class DefaultPlaybookTest : BaseTest() {
registrationToken = "token",
temporaryExposureKeys = listOf(),
consentToFederation = true,
visitedCountries = listOf("DE")
visitedCountries = listOf("DE"),
checkIns = emptyList()
)
)

Expand All @@ -107,7 +108,8 @@ class DefaultPlaybookTest : BaseTest() {
registrationToken = "token",
temporaryExposureKeys = listOf(),
consentToFederation = true,
visitedCountries = listOf("DE")
visitedCountries = listOf("DE"),
checkIns = emptyList()
)
)
} catch (e: Exception) {
Expand All @@ -126,7 +128,8 @@ class DefaultPlaybookTest : BaseTest() {
registrationToken = "token",
temporaryExposureKeys = listOf(),
consentToFederation = true,
visitedCountries = listOf("DE")
visitedCountries = listOf("DE"),
checkIns = emptyList()
)
)
} catch (e: Exception) {
Expand All @@ -146,7 +149,8 @@ class DefaultPlaybookTest : BaseTest() {
registrationToken = "token",
temporaryExposureKeys = listOf(),
consentToFederation = true,
visitedCountries = listOf("DE")
visitedCountries = listOf("DE"),
checkIns = emptyList()
)
)
}
Expand Down Expand Up @@ -260,7 +264,8 @@ class DefaultPlaybookTest : BaseTest() {
registrationToken = "token",
temporaryExposureKeys = listOf(),
consentToFederation = true,
visitedCountries = listOf("DE")
visitedCountries = listOf("DE"),
checkIns = emptyList()
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ class SubmissionServerTest : BaseTest() {
authCode = "testAuthCode",
keyList = listOf(googleKeyList),
consentToFederation = true,
visitedCountries = listOf("DE")
visitedCountries = listOf("DE"),
checkIns = emptyList()
)
server.submitKeysToServer(submissionData)

Expand Down Expand Up @@ -144,7 +145,8 @@ class SubmissionServerTest : BaseTest() {
authCode = "39ec4930-7a1f-4d5d-921f-bfad3b6f1269",
keyList = listOf(googleKeyList),
consentToFederation = true,
visitedCountries = listOf("DE")
visitedCountries = listOf("DE"),
checkIns = emptyList()
)
webServer.enqueue(MockResponse().setBody("{}"))
server.submitKeysToServer(submissionData)
Expand Down
Loading

0 comments on commit fc8ece0

Please sign in to comment.