From 5c0d5eff316167b46b64ea33ad61235b8b5966a8 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 14:52:04 +0100 Subject: [PATCH 01/16] Rename --- .../coronawarnapp/srs/core/server/SrsAuthorizationServer.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt index b1bf4fe92f2..e3aca578d96 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt @@ -38,7 +38,7 @@ class SrsAuthorizationServer @Inject constructor( suspend fun authorize(request: SrsAuthorizationRequest): Instant = withContext(dispatcherProvider.IO) { try { - authoriseRequest(request) + authorizeRequest(request) } catch (e: Exception) { throw when (e) { is SrsSubmissionException -> e @@ -51,7 +51,7 @@ class SrsAuthorizationServer @Inject constructor( } } - private suspend fun authoriseRequest(request: SrsAuthorizationRequest): Instant { + private suspend fun authorizeRequest(request: SrsAuthorizationRequest): Instant { Timber.tag(TAG).d("authorize(request=%s)", request) val srsOtpRequest = SRSOneTimePasswordRequestAndroid.newBuilder() .setPayload( From 3e4b2bce4f37a9d7c412c5f36b38c286a109e35b Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 15:44:59 +0100 Subject: [PATCH 02/16] Fake OTP auth --- .../core/model/SrsAuthorizationFakeRequest.kt | 6 ++++ .../srs/core/playbook/SrsPlaybook.kt | 5 +++ .../repository/SrsSubmissionRepository.kt | 13 +++++-- .../srs/core/server/SrsAuthorizationServer.kt | 36 ++++++++++++++++++- .../srs/core/server/SrsSubmissionServer.kt | 2 +- .../de/rki/coronawarnapp/util/PaddingTool.kt | 6 ++++ .../ppdd/srs_otp_request_android.proto | 2 ++ .../internal/v2/ppdd_ppa_parameters.proto | 5 +++ 8 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/model/SrsAuthorizationFakeRequest.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/model/SrsAuthorizationFakeRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/model/SrsAuthorizationFakeRequest.kt new file mode 100644 index 00000000000..1c19fbf78b9 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/model/SrsAuthorizationFakeRequest.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.srs.core.model + +data class SrsAuthorizationFakeRequest( + val safetyNetJws: String, + val salt: String, +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/playbook/SrsPlaybook.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/playbook/SrsPlaybook.kt index c8b63763890..a3c8ba298bf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/playbook/SrsPlaybook.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/playbook/SrsPlaybook.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.srs.core.playbook +import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationFakeRequest import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationRequest import de.rki.coronawarnapp.srs.core.model.SrsSubmissionPayload import de.rki.coronawarnapp.srs.core.server.SrsAuthorizationServer @@ -18,6 +19,10 @@ class SrsPlaybook @Inject constructor( return srsAuthorizationServer.authorize(request) } + suspend fun fakeAuthorize(request: SrsAuthorizationFakeRequest) { + srsAuthorizationServer.fakeAuthorize(request) + } + suspend fun submit(payLoad: SrsSubmissionPayload) { srsSubmissionServer.submit(payLoad) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepository.kt index 932c867cabf..4d9b29df26e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepository.kt @@ -15,6 +15,7 @@ import de.rki.coronawarnapp.server.protocols.internal.ppdd.SrsOtp.SRSOneTimePass import de.rki.coronawarnapp.srs.core.AndroidIdProvider import de.rki.coronawarnapp.srs.core.SubmissionReporter import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException +import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationFakeRequest import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationRequest import de.rki.coronawarnapp.srs.core.model.SrsDeviceAttestationRequest import de.rki.coronawarnapp.srs.core.model.SrsOtp @@ -57,7 +58,16 @@ class SrsSubmissionRepository @Inject constructor( val nowUtc = timeStamper.nowUTC var srsOtp = currentOtp(nowUtc) val attestResult = attest(appConfig, srsOtp, srsDevSettings.checkLocalPrerequisites()) - if (!srsOtp.isValid(nowUtc)) { + + if (srsOtp.isValid(nowUtc)) { + Timber.d("Otp is still valid -> fakePlaybookAuthorization") + playbook.fakeAuthorize( + SrsAuthorizationFakeRequest( + safetyNetJws = attestResult.report.jwsResult, + salt = String(attestResult.ourSalt), + ) + ) + } else { Timber.d("Authorize new srsOtp=%s", srsOtp) val expiresAt = playbook.authorize( SrsAuthorizationRequest( @@ -67,7 +77,6 @@ class SrsSubmissionRepository @Inject constructor( androidId = androidIdProvider.getAndroidId() ) ) - srsOtp = srsOtp.copy(expiresAt = expiresAt) srsSubmissionSettings.setOtp(srsOtp) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt index e3aca578d96..7dc7eada964 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.srs.core.server import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue +import com.google.protobuf.ByteString import javax.inject.Inject import dagger.Lazy import dagger.Reusable @@ -13,14 +14,18 @@ import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid import de.rki.coronawarnapp.server.protocols.internal.ppdd.SrsOtpRequestAndroid.SRSOneTimePasswordRequestAndroid import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException.ErrorCode +import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationFakeRequest import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationRequest import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationResponse import de.rki.coronawarnapp.srs.core.storage.SrsDevSettings import de.rki.coronawarnapp.tag +import de.rki.coronawarnapp.util.PaddingTool import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.serialization.BaseJackson import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext +import okhttp3.ResponseBody +import retrofit2.Response import timber.log.Timber import java.time.Instant import java.time.OffsetDateTime @@ -32,6 +37,7 @@ class SrsAuthorizationServer @Inject constructor( private val dispatcherProvider: DispatcherProvider, private val srsDevSettings: SrsDevSettings, private val appConfigProvider: AppConfigProvider, + private val paddingTool: PaddingTool, ) { private val api = srsAuthorizationApi.get() @@ -51,6 +57,28 @@ class SrsAuthorizationServer @Inject constructor( } } + suspend fun fakeAuthorize(request: SrsAuthorizationFakeRequest): Result> = runCatching { + Timber.tag(TAG).d("fakeAuthorize()") + val authPadding = paddingTool.srsAuthPadding(appConfigProvider.currentConfig.first().selfReportSubmission) + val srsOtpRequest = SRSOneTimePasswordRequestAndroid.newBuilder() + .setAuthentication( + PpacAndroid.PPACAndroid.newBuilder() + .setSafetyNetJws(request.safetyNetJws) + .setSalt(request.salt) + .build() + ) + .setRequestPadding(ByteString.copyFromUtf8(authPadding)) // TBD + .build() + + val headers = mapOf( + "Content-Type" to "application/x-protobuf", + "cwa-fake" to "1", + ) + api.authenticate(headers, srsOtpRequest) + }.onFailure { + Timber.tag(TAG).d("fakeAuthorize() failed ->%s", it.localizedMessage) + } + private suspend fun authorizeRequest(request: SrsAuthorizationRequest): Instant { Timber.tag(TAG).d("authorize(request=%s)", request) val srsOtpRequest = SRSOneTimePasswordRequestAndroid.newBuilder() @@ -69,7 +97,10 @@ class SrsAuthorizationServer @Inject constructor( ) .build() - val headers = mutableMapOf("Content-Type" to "application/x-protobuf").apply { + val headers = mutableMapOf( + "Content-Type" to "application/x-protobuf", + "cwa-fake" to "0", + ).apply { if (srsDevSettings.forceAndroidIdAcceptance()) { Timber.tag(TAG).d("forceAndroidIdAcceptance is enabled") put("cwa-ppac-android-accept-android-id", "1") @@ -85,6 +116,7 @@ class SrsAuthorizationServer @Inject constructor( errorArgs = errorArgs(errorCode) ) } + response?.expirationDate != null -> OffsetDateTime.parse(response.expirationDate).toInstant() else -> throw when (bodyResponse.code()) { 400 -> SrsSubmissionException(ErrorCode.SRS_OTP_400) @@ -107,6 +139,7 @@ class SrsAuthorizationServer @Inject constructor( .timeBetweenSubmissionsInDays .toDays() ) + ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED -> { val hours = appConfigProvider .currentConfig @@ -117,6 +150,7 @@ class SrsAuthorizationServer @Inject constructor( .toHours() arrayOf(hours, hours) } + else -> emptyArray() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsSubmissionServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsSubmissionServer.kt index 3c7cec018c6..ce3c70f7c15 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsSubmissionServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsSubmissionServer.kt @@ -30,7 +30,7 @@ class SrsSubmissionServer @Inject constructor( suspend fun submit(payload: SrsSubmissionPayload) = withContext(dispatcherProvider.IO) { try { - Timber.tag(TAG).d("submit()") + Timber.tag(TAG).d("submit(payload=%s)", payload) submitPayload(payload) } catch (e: Exception) { throw when (e) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt index e5b2ee7f3b1..982dc0b6a08 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.util import androidx.annotation.VisibleForTesting import dagger.Reusable import de.rki.coronawarnapp.appconfig.PlausibleDeniabilityParametersContainer +import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig import de.rki.coronawarnapp.risk.DefaultRiskLevels.Companion.inRange import de.rki.coronawarnapp.server.protocols.internal.v2.PresenceTracingParametersOuterClass.PresenceTracingPlausibleDeniabilityParameters.NumberOfFakeCheckInsFunctionParametersOrBuilder import de.rki.coronawarnapp.submission.server.SubmissionServer @@ -99,6 +100,11 @@ class PaddingTool @Inject constructor( return requestPadding(numberOfBytes) } + fun srsAuthPadding(selfReportSubmission: SelfReportSubmissionConfig): String { + return (0..10).map { PADDING_ITEMS.random(sourceFast) } // TODO finalise it + .joinToString("") + } + companion object { private val PADDING_ITEMS = ('A'..'Z') + ('a'..'z') + ('0'..'9') private const val MIN_KEY_COUNT_FOR_SUBMISSION = 15 // Increased from 14 to 15 in purpose for CheckIn submission diff --git a/Server-Protocol-Buffer/src/main/proto/internal/ppdd/srs_otp_request_android.proto b/Server-Protocol-Buffer/src/main/proto/internal/ppdd/srs_otp_request_android.proto index 2a430f6a9fe..b47efa23652 100644 --- a/Server-Protocol-Buffer/src/main/proto/internal/ppdd/srs_otp_request_android.proto +++ b/Server-Protocol-Buffer/src/main/proto/internal/ppdd/srs_otp_request_android.proto @@ -10,6 +10,8 @@ message SRSOneTimePasswordRequestAndroid { SRSOneTimePassword payload = 2; + bytes requestPadding = 3; + message SRSOneTimePassword { string otp = 1; bytes androidId = 2; diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_ppa_parameters.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_ppa_parameters.proto index 73c1f39aa03..0ea8158a9b8 100644 --- a/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_ppa_parameters.proto +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_ppa_parameters.proto @@ -19,4 +19,9 @@ message PPDDPrivacyPreservingAnalyticsParametersCommon { double probabilityToSubmitExposureWindows = 2; int32 hoursSinceTestRegistrationToSubmitTestResultMetadata = 3; int32 hoursSinceTestResultToSubmitKeySubmissionMetadata = 4; + PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters plausibleDeniabilityParameters = 5; +} + +message PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters { + double probabilityOfFakeKeySubmission = 1; } \ No newline at end of file From 13116fab954de217b1e174a1dfb9ddd38513c026 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 15:46:27 +0100 Subject: [PATCH 03/16] Pass paddingTool --- .../srs/core/server/SrsAuthorizationServerTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt index 77770b72324..98dca21011d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException.ErrorCode import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationRequest import de.rki.coronawarnapp.srs.core.model.SrsOtp import de.rki.coronawarnapp.srs.core.storage.SrsDevSettings +import de.rki.coronawarnapp.util.PaddingTool import de.rki.coronawarnapp.util.serialization.SerializationModule import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe @@ -31,6 +32,7 @@ import testhelpers.BaseTest import testhelpers.TestDispatcherProvider import testhelpers.extensions.toInstant import java.time.DateTimeException +import kotlin.random.Random internal class SrsAuthorizationServerTest : BaseTest() { @@ -38,6 +40,7 @@ internal class SrsAuthorizationServerTest : BaseTest() { @MockK lateinit var srsDevSettings: SrsDevSettings @MockK lateinit var appConfigProvider: AppConfigProvider @MockK lateinit var configData: ConfigData + private val paddingTool = PaddingTool(Random) private val request = SrsAuthorizationRequest( srsOtp = SrsOtp(), @@ -171,5 +174,6 @@ internal class SrsAuthorizationServerTest : BaseTest() { mapper = SerializationModule.jacksonBaseMapper, srsDevSettings = srsDevSettings, appConfigProvider = appConfigProvider, + paddingTool = paddingTool, ) } From acd1ab57f6a0bfcdab0b4493f873c925f60f2efa Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 16:06:05 +0100 Subject: [PATCH 04/16] Log --- .../rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt index b7e8edbfeeb..5cedd54a0fc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt @@ -76,7 +76,7 @@ class SrsAuthorizationServer @Inject constructor( ) api.authenticate(headers, srsOtpRequest) }.onFailure { - Timber.tag(TAG).d("fakeAuthorize() failed ->%s", it.localizedMessage) + Timber.tag(TAG).d("fakeAuthorize() failed -> %s", it.localizedMessage) } private suspend fun authorizeRequest(request: SrsAuthorizationRequest): Instant { From a7963ba2915b1e3b90a308c64c8890b9c7eec749 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 16:06:21 +0100 Subject: [PATCH 05/16] Cover fake requests --- .../core/server/SrsAuthorizationServerTest.kt | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt index 1d2fcc272b0..2501b14eb96 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt @@ -9,11 +9,13 @@ import de.rki.coronawarnapp.exception.http.NetworkConnectTimeoutException import de.rki.coronawarnapp.exception.http.NetworkReadTimeoutException import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException.ErrorCode +import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationFakeRequest import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationRequest import de.rki.coronawarnapp.srs.core.model.SrsOtp import de.rki.coronawarnapp.srs.core.storage.SrsDevSettings import de.rki.coronawarnapp.util.PaddingTool import de.rki.coronawarnapp.util.serialization.SerializationModule +import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf @@ -73,6 +75,7 @@ internal class SrsAuthorizationServerTest : BaseTest() { fun `force accept android id - on`() = runTest { val headers = mapOf( "Content-Type" to "application/x-protobuf", + "cwa-fake" to "0", "cwa-ppac-android-accept-android-id" to "1" ) coEvery { srsDevSettings.forceAndroidIdAcceptance() } returns true @@ -82,11 +85,41 @@ internal class SrsAuthorizationServerTest : BaseTest() { @Test fun `force accept android id - off`() = runTest { - val headers = mapOf("Content-Type" to "application/x-protobuf") + val headers = mapOf( + "Content-Type" to "application/x-protobuf", + "cwa-fake" to "0" + ) instance().authorize(request) shouldBe "2023-05-16T08:34:00+00:00".toInstant() coVerify { srsAuthorizationApi.authenticate(headers, any()) } } + @Test + fun `fake srs auth`() = runTest { + val headers = mapOf( + "Content-Type" to "application/x-protobuf", + "cwa-fake" to "1" + ) + val fakeRequest = SrsAuthorizationFakeRequest( + salt = "", + safetyNetJws = "" + ) + instance().fakeAuthorize(fakeRequest) + coVerify { srsAuthorizationApi.authenticate(headers, any()) } + } + + @Test + fun `fake srs auth should not throw any error`() = runTest { + coEvery { srsAuthorizationApi.authenticate(any(), any()) } throws Exception("Surprise!") + val fakeRequest = SrsAuthorizationFakeRequest( + salt = "", + safetyNetJws = "" + ) + + shouldNotThrowAny { + instance().fakeAuthorize(fakeRequest) + } + } + @Test fun `invalid expiry time`() = runTest { coEvery { srsAuthorizationApi.authenticate(any(), any()) } returns Response.success( From 534510f84d5fec1966f64f647de8e28233de06d6 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 16:16:21 +0100 Subject: [PATCH 06/16] Cover fake requests --- .../repository/SrsSubmissionRepositoryTest.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepositoryTest.kt index e5b41194299..238ea243f8a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/repository/SrsSubmissionRepositoryTest.kt @@ -31,6 +31,7 @@ import io.kotest.matchers.shouldNotBe import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.coVerifySequence import io.mockk.every import io.mockk.impl.annotations.MockK @@ -101,6 +102,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() { coEvery { appConfigProvider.getAppConfig() } returns configData coEvery { playbook.authorize(any()) } returns Instant.parse("2023-11-07T12:10:10Z") coEvery { playbook.submit(any()) } just Runs + coEvery { playbook.fakeAuthorize(any()) } just Runs coEvery { srsDevSettings.checkLocalPrerequisites() } returns true coEvery { submissionReporter.reportAt(any()) } just Runs } @@ -125,6 +127,10 @@ internal class SrsSubmissionRepositoryTest : BaseTest() { timeStamper.nowUTC submissionReporter.reportAt(any()) } + + coVerify(exactly = 0) { + playbook.fakeAuthorize(any()) + } } @Test @@ -151,6 +157,10 @@ internal class SrsSubmissionRepositoryTest : BaseTest() { timeStamper.nowUTC submissionReporter.reportAt(any()) } + + coVerify(exactly = 0) { + playbook.fakeAuthorize(any()) + } } @Test @@ -161,6 +171,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() { appConfigProvider.getAppConfig() timeStamper.nowUTC srsSubmissionSettings.getOtp() + playbook.fakeAuthorize(any()) tekStorage.tekData tekCalculations.transformToKeyHistoryInExternalFormat(any(), any()) checkInsRepo.completedCheckIns @@ -171,6 +182,10 @@ internal class SrsSubmissionRepositoryTest : BaseTest() { timeStamper.nowUTC submissionReporter.reportAt(any()) } + + coVerify(exactly = 0) { + playbook.authorize(any()) + } } @Test From 41fb774d3825c5d864be0268c7ed9d1390b60648 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 16:17:17 +0100 Subject: [PATCH 07/16] Remove comment --- .../rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt index 5cedd54a0fc..abdb5baac14 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt @@ -67,7 +67,7 @@ class SrsAuthorizationServer @Inject constructor( .setSalt(request.salt) .build() ) - .setRequestPadding(ByteString.copyFromUtf8(authPadding)) // TBD + .setRequestPadding(ByteString.copyFromUtf8(authPadding)) .build() val headers = mapOf( From a63bfda7f61bbc9ea72955668b4d861b78e4ffe7 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 16:44:28 +0100 Subject: [PATCH 08/16] Update SrsAuthorizationServerTest.kt --- .../core/server/SrsAuthorizationServerTest.kt | 68 ++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt index 2501b14eb96..25b3fe99c0f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServerTest.kt @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfigContainer import de.rki.coronawarnapp.exception.http.CwaUnknownHostException import de.rki.coronawarnapp.exception.http.NetworkConnectTimeoutException import de.rki.coronawarnapp.exception.http.NetworkReadTimeoutException +import de.rki.coronawarnapp.server.protocols.internal.ppdd.SrsOtpRequestAndroid import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException.ErrorCode import de.rki.coronawarnapp.srs.core.model.SrsAuthorizationFakeRequest @@ -34,6 +35,7 @@ import testhelpers.BaseTest import testhelpers.TestDispatcherProvider import testhelpers.extensions.toInstant import java.time.DateTimeException +import java.util.UUID import kotlin.random.Random internal class SrsAuthorizationServerTest : BaseTest() { @@ -46,7 +48,7 @@ internal class SrsAuthorizationServerTest : BaseTest() { private val request = SrsAuthorizationRequest( srsOtp = SrsOtp(), - safetyNetJws = "wwrr", + safetyNetJws = "safetyNetJws", salt = "salt", androidId = ByteString.EMPTY ) @@ -54,13 +56,32 @@ internal class SrsAuthorizationServerTest : BaseTest() { @BeforeEach fun setUp() { MockKAnnotations.init(this) - coEvery { srsAuthorizationApi.authenticate(any(), any()) } returns Response.success( - """ - { - "expirationDate": "2023-05-16T08:34:00+00:00" - } - """.trimIndent().toResponseBody() - ) + coEvery { srsAuthorizationApi.authenticate(any(), any()) } answers { + args[0] shouldBe mapOf( + "Content-Type" to "application/x-protobuf", + "cwa-fake" to "0" + ) + + val body = args[1] + body.shouldBeInstanceOf() + body.authentication.apply { + salt shouldBe "salt" + safetyNetJws shouldBe "safetyNetJws" + } + + body.payload.apply { + androidId shouldBe ByteString.EMPTY + shouldNotThrowAny { UUID.fromString(otp) } + } + + Response.success( + """ + { + "expirationDate": "2023-05-16T08:34:00+00:00" + } + """.trimIndent().toResponseBody() + ) + } coEvery { srsDevSettings.forceAndroidIdAcceptance() } returns false every { configData.selfReportSubmission } returns SelfReportSubmissionConfigContainer.DEFAULT every { appConfigProvider.currentConfig } returns flowOf(configData) @@ -73,6 +94,13 @@ internal class SrsAuthorizationServerTest : BaseTest() { @Test fun `force accept android id - on`() = runTest { + coEvery { srsAuthorizationApi.authenticate(any(), any()) } returns Response.success( + """ + { + "expirationDate": "2023-05-16T08:34:00+00:00" + } + """.trimIndent().toResponseBody() + ) val headers = mapOf( "Content-Type" to "application/x-protobuf", "cwa-fake" to "0", @@ -107,6 +135,30 @@ internal class SrsAuthorizationServerTest : BaseTest() { coVerify { srsAuthorizationApi.authenticate(headers, any()) } } + @Test + fun `fake srs auth body`() = runTest { + coEvery { srsAuthorizationApi.authenticate(any(), any()) } answers { + args[0] shouldBe mapOf( + "Content-Type" to "application/x-protobuf", + "cwa-fake" to "1" + ) + + val body = args[1] + body.shouldBeInstanceOf() + body.authentication.safetyNetJws shouldBe "safetyNetJws" + body.authentication.salt shouldBe "salt" + body.hasPayload() shouldBe false + + Response.success("".toResponseBody()) + } + + val fakeRequest = SrsAuthorizationFakeRequest( + salt = "salt", + safetyNetJws = "safetyNetJws" + ) + instance().fakeAuthorize(fakeRequest) + } + @Test fun `fake srs auth should not throw any error`() = runTest { coEvery { srsAuthorizationApi.authenticate(any(), any()) } throws Exception("Surprise!") From 23d86f521452e98109c18d0708e3ad877e159839 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Tue, 15 Nov 2022 20:55:11 +0100 Subject: [PATCH 09/16] Analytics fake key submission --- .../appconfig/AnalyticsConfig.kt | 4 +++ .../mapping/AnalyticsConfigMapper.kt | 24 ++++++++++++++--- .../datadonation/analytics/Analytics.kt | 26 +++++++++++++++++-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt index 8f361bcc04c..bacaa5918eb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt @@ -10,6 +10,10 @@ interface AnalyticsConfig { val hoursSinceTestResultToSubmitKeySubmissionMetadata: Int val probabilityToSubmitNewExposureWindows: Double val analyticsEnabled: Boolean + val plausibleDeniabilityParameters: PlausibleDeniabilityParameters + interface PlausibleDeniabilityParameters{ + val probabilityOfFakeKeySubmission: Double + } interface Mapper : ConfigMapper } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt index cdc2e6970a4..591db423139 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt @@ -4,7 +4,10 @@ import dagger.Reusable import de.rki.coronawarnapp.appconfig.AnalyticsConfig import de.rki.coronawarnapp.appconfig.SafetyNetRequirements import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer +import de.rki.coronawarnapp.appconfig.mapping.AnalyticsConfigMapper.PlausibleDeniabilityParametersContainer import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid +import de.rki.coronawarnapp.server.protocols.internal.v2.PpddPpaParameters +import de.rki.coronawarnapp.server.protocols.internal.v2.PpddPpaParameters.PPDDPrivacyPreservingAnalyticsParametersCommon import timber.log.Timber import javax.inject.Inject @@ -22,7 +25,8 @@ class AnalyticsConfigMapper @Inject constructor() : AnalyticsConfig.Mapper { hoursSinceTestRegistrationToSubmitTestResultMetadata = 0, hoursSinceTestResultToSubmitKeySubmissionMetadata = 0, probabilityToSubmitNewExposureWindows = 0.0, - analyticsEnabled = false + analyticsEnabled = false, + plausibleDeniabilityParameters = PlausibleDeniabilityParametersContainer() ) } @@ -55,7 +59,8 @@ class AnalyticsConfigMapper @Inject constructor() : AnalyticsConfig.Mapper { .hoursSinceTestRegistrationToSubmitTestResultMetadata, probabilityToSubmitNewExposureWindows = it .probabilityToSubmitExposureWindows, - analyticsEnabled = true + analyticsEnabled = true, + plausibleDeniabilityParameters = it.mapPlausibleDeniabilityParameters() ) } @@ -65,6 +70,19 @@ class AnalyticsConfigMapper @Inject constructor() : AnalyticsConfig.Mapper { override val hoursSinceTestRegistrationToSubmitTestResultMetadata: Int, override val hoursSinceTestResultToSubmitKeySubmissionMetadata: Int, override val probabilityToSubmitNewExposureWindows: Double, - override val analyticsEnabled: Boolean + override val analyticsEnabled: Boolean, + override val plausibleDeniabilityParameters: AnalyticsConfig.PlausibleDeniabilityParameters, ) : AnalyticsConfig + + data class PlausibleDeniabilityParametersContainer( + override val probabilityOfFakeKeySubmission: Double = 0.0 + ) : AnalyticsConfig.PlausibleDeniabilityParameters +} + +private fun PPDDPrivacyPreservingAnalyticsParametersCommon.mapPlausibleDeniabilityParameters() = when { + hasPlausibleDeniabilityParameters() -> PlausibleDeniabilityParametersContainer( + probabilityOfFakeKeySubmission = plausibleDeniabilityParameters.probabilityOfFakeKeySubmission + ) + + else -> PlausibleDeniabilityParametersContainer() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt index 7127d6273de..e19db0837bc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt @@ -13,11 +13,13 @@ import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException.Type.ATTESTATION_REQUEST_FAILED import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException.Type.INTERNAL_ERROR +import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaDataRequestAndroid import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.reset.Resettable +import de.rki.coronawarnapp.util.security.RandomStrong import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first @@ -40,7 +42,9 @@ class Analytics @Inject constructor( private val settings: AnalyticsSettings, private val logger: LastAnalyticsSubmissionLogger, private val timeStamper: TimeStamper, - private val onboardingSettings: OnboardingSettings + private val onboardingSettings: OnboardingSettings, + @RandomStrong private val randomSource: Random, + private val playbook: Playbook, ) : Resettable { private val submissionLockoutMutex = Mutex() @@ -123,9 +127,11 @@ class Analytics @Inject constructor( val result = try { // 6min, if attestation and/or submission takes longer than that, // then we want to give modules still time to cleanup and get into a consistent state. - withTimeout(360_000) { + val analytics = withTimeout(360_000) { trySubmission(configData.analytics, analyticsProto) } + tryFakeKeySubmission(configData) + analytics } catch (e: TimeoutCancellationException) { Timber.tag(TAG).e(e, "trySubmission() timed out after 360s.") Result(successful = false, shouldRetry = true) @@ -153,6 +159,22 @@ class Analytics @Inject constructor( return result } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + suspend fun tryFakeKeySubmission(configData: ConfigData) = runCatching { + val probability = configData + .analytics + .plausibleDeniabilityParameters + .probabilityOfFakeKeySubmission + if (randomSource.nextDouble() <= probability) { + Timber.tag(TAG).d("fake key submission") + playbook.submitFake() + } else { + Timber.tag(TAG).d("Skip fake key submission") + } + }.onFailure { + Timber.tag(TAG).d("tryFakeKeySubmission -> ${it.localizedMessage}") + } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun stopDueToNoAnalyticsConfig(analyticsConfig: AnalyticsConfig): Boolean { return !analyticsConfig.analyticsEnabled From c452fafc06d7a6a9b009d1a3134ea7d07d3d5fc5 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Wed, 16 Nov 2022 10:43:26 +0100 Subject: [PATCH 10/16] lint --- .../de/rki/coronawarnapp/datadonation/analytics/Analytics.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt index e19db0837bc..17dbcc181d3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt @@ -33,6 +33,7 @@ import javax.inject.Singleton import kotlin.random.Random @Singleton +@Suppress("LongParameterList") class Analytics @Inject constructor( private val dataDonationAnalyticsServer: DataDonationAnalyticsServer, private val appConfigProvider: AppConfigProvider, From c5fdd7a2e048a42a4b9eb64bdf953d77c484ed68 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Wed, 16 Nov 2022 10:43:32 +0100 Subject: [PATCH 11/16] Update AnalyticsTest.kt --- .../datadonation/analytics/AnalyticsTest.kt | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt index 8769c623417..1894fe24de4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt @@ -5,6 +5,7 @@ import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.appconfig.SafetyNetRequirements import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer +import de.rki.coronawarnapp.appconfig.mapping.AnalyticsConfigMapper import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.datadonation.analytics.modules.exposureriskmetadata.ExposureRiskMetadataDonor import de.rki.coronawarnapp.datadonation.analytics.modules.usermetadata.UserMetadataDonor @@ -13,6 +14,7 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.datadonation.analytics.storage.LastAnalyticsSubmissionLogger import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException +import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaDataRequestAndroid import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid @@ -39,6 +41,7 @@ import org.junit.jupiter.api.Test import testhelpers.BaseTest import testhelpers.preferences.mockFlowPreference import java.time.Duration +import kotlin.random.Random class AnalyticsTest : BaseTest() { @MockK lateinit var dataDonationAnalyticsServer: DataDonationAnalyticsServer @@ -51,6 +54,7 @@ class AnalyticsTest : BaseTest() { @MockK lateinit var lastAnalyticsSubmissionLogger: LastAnalyticsSubmissionLogger @MockK lateinit var timeStamper: TimeStamper @MockK lateinit var onboardingSettings: OnboardingSettings + @MockK lateinit var playbook: Playbook private val baseTime: Instant = Instant.ofEpochMilli(0) @@ -69,6 +73,10 @@ class AnalyticsTest : BaseTest() { every { settings.analyticsEnabled } returns mockFlowPreference(true) every { analyticsConfig.probabilityToSubmit } returns 1.0 + every { analyticsConfig.plausibleDeniabilityParameters } returns + AnalyticsConfigMapper.PlausibleDeniabilityParametersContainer() + + coEvery { playbook.submitFake() } just Runs val twoDaysAgo = baseTime.minus(Duration.ofDays(2)) every { settings.lastSubmittedTimestamp } returns mockFlowPreference(twoDaysAgo) @@ -88,7 +96,9 @@ class AnalyticsTest : BaseTest() { settings = settings, logger = lastAnalyticsSubmissionLogger, timeStamper = timeStamper, - onboardingSettings = onboardingSettings + onboardingSettings = onboardingSettings, + playbook = playbook, + randomSource = Random ) ) @@ -222,7 +232,67 @@ class AnalyticsTest : BaseTest() { } @Test - fun `submit analytics data`() { + fun `submit analytics data - no fake key submission`() { + val metadata = PpaData.ExposureRiskMetadata.newBuilder() + .setRiskLevel(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) + .setMostRecentDateAtRiskLevel(baseTime.toEpochMilli()) + .setDateChangedComparedToPreviousSubmission(true) + .setRiskLevelChangedComparedToPreviousSubmission(true) + .build() + + val payload = PpaData.PPADataAndroid.newBuilder() + .addExposureRiskMetadataSet(metadata) + .build() + + val analyticsRequest = PpaDataRequestAndroid.PPADataRequestAndroid.newBuilder() + .setPayload(payload) + .setAuthentication(PpacAndroid.PPACAndroid.getDefaultInstance()) + .build() + + val donationRequestSlot = slot() + coEvery { exposureRiskMetadataDonor.beginDonation(capture(donationRequestSlot)) } returns + ExposureRiskMetadataDonor.ExposureRiskMetadataContribution( + contributionProto = metadata, + onContributionFinished = {} + ) + + coEvery { deviceAttestation.attest(any()) } returns + object : DeviceAttestation.Result { + override val accessControlProtoBuf: PpacAndroid.PPACAndroid + get() = PpacAndroid.PPACAndroid.getDefaultInstance() + + override fun requirePass(requirements: SafetyNetRequirements) {} + } + + val analytics = createInstance() + + runTest { + val result = analytics.submitIfWanted() + result.apply { + successful shouldBe true + shouldRetry shouldBe false + } + } + + donationRequestSlot.captured.currentConfig shouldBe configData + + coVerify(exactly = 1) { + analytics.submitAnalyticsData(configData) + dataDonationAnalyticsServer.uploadAnalyticsData(analyticsRequest) + } + + coVerify(exactly = 0) { + playbook.submitFake() + } + } + + @Test + fun `submit analytics data - fake key submission`() { + every { analyticsConfig.plausibleDeniabilityParameters } returns + AnalyticsConfigMapper.PlausibleDeniabilityParametersContainer( + probabilityOfFakeKeySubmission = 1.0 + ) + val metadata = PpaData.ExposureRiskMetadata.newBuilder() .setRiskLevel(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) .setMostRecentDateAtRiskLevel(baseTime.toEpochMilli()) @@ -269,6 +339,7 @@ class AnalyticsTest : BaseTest() { coVerify(exactly = 1) { analytics.submitAnalyticsData(configData) dataDonationAnalyticsServer.uploadAnalyticsData(analyticsRequest) + playbook.submitFake() } } From eec9b1926ab0f2c3d098bcdc866d1cc51520634d Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Wed, 16 Nov 2022 11:30:52 +0100 Subject: [PATCH 12/16] Padding --- .../PlausibleDeniabilityParametersContainer.kt | 3 +-- .../appconfig/SelfReportSubmissionConfig.kt | 9 ++++++++- .../srs/core/server/SrsAuthorizationServer.kt | 9 +++++++-- .../main/java/de/rki/coronawarnapp/util/PaddingTool.kt | 8 ++++---- .../java/de/rki/coronawarnapp/util/PaddingToolTest.kt | 10 ++++++++++ 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/PlausibleDeniabilityParametersContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/PlausibleDeniabilityParametersContainer.kt index 83ae06f20d2..13da067917d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/PlausibleDeniabilityParametersContainer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/PlausibleDeniabilityParametersContainer.kt @@ -1,7 +1,6 @@ package de.rki.coronawarnapp.appconfig -import de.rki.coronawarnapp.server.protocols.internal.v2.PresenceTracingParametersOuterClass - .PresenceTracingPlausibleDeniabilityParameters.NumberOfFakeCheckInsFunctionParametersOrBuilder +import de.rki.coronawarnapp.server.protocols.internal.v2.PresenceTracingParametersOuterClass.PresenceTracingPlausibleDeniabilityParameters.NumberOfFakeCheckInsFunctionParametersOrBuilder data class PlausibleDeniabilityParametersContainer( val checkInSizesInBytes: List = emptyList(), diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/SelfReportSubmissionConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/SelfReportSubmissionConfig.kt index c5cc4b74255..08bf8b51331 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/SelfReportSubmissionConfig.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/SelfReportSubmissionConfig.kt @@ -14,11 +14,18 @@ interface SelfReportSubmissionConfig { interface SelfReportSubmissionCommon { val timeSinceOnboardingInHours: Duration val timeBetweenSubmissionsInDays: Duration + val plausibleDeniabilityParameters: SrsPlausibleDeniabilityParameters } +data class SrsPlausibleDeniabilityParameters( + val minRequestPaddingBytes: Int = 0, + val maxRequestPaddingBytes: Int = 0 +) + data class SelfReportSubmissionCommonContainer( override val timeSinceOnboardingInHours: Duration = DEFAULT_HOURS, - override val timeBetweenSubmissionsInDays: Duration = DEFAULT_DAYS + override val timeBetweenSubmissionsInDays: Duration = DEFAULT_DAYS, + override val plausibleDeniabilityParameters: SrsPlausibleDeniabilityParameters = SrsPlausibleDeniabilityParameters() ) : SelfReportSubmissionCommon { companion object { val DEFAULT_HOURS: Duration = Duration.ofHours(24) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt index abdb5baac14..12c562dc5a9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/server/SrsAuthorizationServer.kt @@ -59,7 +59,12 @@ class SrsAuthorizationServer @Inject constructor( suspend fun fakeAuthorize(request: SrsAuthorizationFakeRequest): Result> = runCatching { Timber.tag(TAG).d("fakeAuthorize()") - val authPadding = paddingTool.srsAuthPadding(appConfigProvider.currentConfig.first().selfReportSubmission) + val selfReportSubmission = appConfigProvider.currentConfig.first().selfReportSubmission + val min = selfReportSubmission.common.plausibleDeniabilityParameters.minRequestPaddingBytes + val max = selfReportSubmission.common.plausibleDeniabilityParameters.maxRequestPaddingBytes + val authPadding = ByteString.copyFrom(paddingTool.srsAuthPadding(min, max)) + + Timber.tag(TAG).d("authPadding=%s, min=%s, max=%s", authPadding, min, max) val srsOtpRequest = SRSOneTimePasswordRequestAndroid.newBuilder() .setAuthentication( PpacAndroid.PPACAndroid.newBuilder() @@ -67,7 +72,7 @@ class SrsAuthorizationServer @Inject constructor( .setSalt(request.salt) .build() ) - .setRequestPadding(ByteString.copyFromUtf8(authPadding)) + .setRequestPadding(authPadding) .build() val headers = mapOf( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt index 982dc0b6a08..c7e2895231c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/PaddingTool.kt @@ -3,7 +3,6 @@ package de.rki.coronawarnapp.util import androidx.annotation.VisibleForTesting import dagger.Reusable import de.rki.coronawarnapp.appconfig.PlausibleDeniabilityParametersContainer -import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig import de.rki.coronawarnapp.risk.DefaultRiskLevels.Companion.inRange import de.rki.coronawarnapp.server.protocols.internal.v2.PresenceTracingParametersOuterClass.PresenceTracingPlausibleDeniabilityParameters.NumberOfFakeCheckInsFunctionParametersOrBuilder import de.rki.coronawarnapp.submission.server.SubmissionServer @@ -100,9 +99,10 @@ class PaddingTool @Inject constructor( return requestPadding(numberOfBytes) } - fun srsAuthPadding(selfReportSubmission: SelfReportSubmissionConfig): String { - return (0..10).map { PADDING_ITEMS.random(sourceFast) } // TODO finalise it - .joinToString("") + fun srsAuthPadding(min: Int, max: Int): ByteArray { + if (min >= max) return byteArrayOf() + val n = sourceFast.nextInt(min, max) + return sourceFast.nextBytes(n) } companion object { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/PaddingToolTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/PaddingToolTest.kt index 4083beaa6a9..0a1f696425e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/PaddingToolTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/PaddingToolTest.kt @@ -76,6 +76,16 @@ class PaddingToolTest : BaseTest() { createInstance().keyPadding(keyListSize = 0).toByteArray().size shouldBe 420 } + @Test + fun `srs request padding`() { + createInstance().srsAuthPadding(0, 0).isEmpty() shouldBe true + createInstance().srsAuthPadding(19, 19).isEmpty() shouldBe true + createInstance().srsAuthPadding(45, 19).isEmpty() shouldBe true + + (createInstance().srsAuthPadding(19, 45).size in 19..45) shouldBe true + (createInstance().srsAuthPadding(1, 100).size in 1..100) shouldBe true + } + @Test fun `keyPadding - genuine request with 5 keys`() { // keyPadding = 10 keys x 28 bytes per key = 252 bytes` From 211ca22fe616664e27a36f551d7caa2e662ba6c0 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Wed, 16 Nov 2022 11:33:07 +0100 Subject: [PATCH 13/16] Lint --- .../java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt index bacaa5918eb..cc6d82c3619 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AnalyticsConfig.kt @@ -12,8 +12,9 @@ interface AnalyticsConfig { val analyticsEnabled: Boolean val plausibleDeniabilityParameters: PlausibleDeniabilityParameters - interface PlausibleDeniabilityParameters{ + interface PlausibleDeniabilityParameters { val probabilityOfFakeKeySubmission: Double } + interface Mapper : ConfigMapper } From 8c36ad741b486575a97fb7102e0e60f634010be9 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Wed, 16 Nov 2022 11:35:43 +0100 Subject: [PATCH 14/16] lint --- .../rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt index 591db423139..bae66ed7350 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/AnalyticsConfigMapper.kt @@ -6,7 +6,6 @@ import de.rki.coronawarnapp.appconfig.SafetyNetRequirements import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer import de.rki.coronawarnapp.appconfig.mapping.AnalyticsConfigMapper.PlausibleDeniabilityParametersContainer import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid -import de.rki.coronawarnapp.server.protocols.internal.v2.PpddPpaParameters import de.rki.coronawarnapp.server.protocols.internal.v2.PpddPpaParameters.PPDDPrivacyPreservingAnalyticsParametersCommon import timber.log.Timber import javax.inject.Inject From 7e1192bc3bb89232ab111882df8bc43c4d0981eb Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Wed, 16 Nov 2022 11:57:50 +0100 Subject: [PATCH 15/16] Maps plausibleDeniabilityParameters --- .../mapping/SelfReportSubmissionConfigMapper.kt | 12 ++++++++++++ .../main/proto/internal/v2/ppdd_srs_parameters.proto | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/SelfReportSubmissionConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/SelfReportSubmissionConfigMapper.kt index 3106888cf76..c9a2e01b730 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/SelfReportSubmissionConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/SelfReportSubmissionConfigMapper.kt @@ -6,6 +6,7 @@ import de.rki.coronawarnapp.appconfig.SelfReportSubmissionCommonContainer import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfigContainer +import de.rki.coronawarnapp.appconfig.SrsPlausibleDeniabilityParameters import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import de.rki.coronawarnapp.server.protocols.internal.v2.PpddSrsParameters import timber.log.Timber @@ -20,6 +21,7 @@ class SelfReportSubmissionConfigMapper @Inject constructor() : SelfReportSubmiss Timber.d("No SelfReportParameters -> set to default") SelfReportSubmissionConfigContainer.DEFAULT } + else -> rawConfig.selfReportParameters.map() } } catch (e: Exception) { @@ -41,6 +43,16 @@ class SelfReportSubmissionConfigMapper @Inject constructor() : SelfReportSubmiss SelfReportSubmissionCommonContainer.DEFAULT_DAYS } else { Duration.ofHours(common.timeBetweenSubmissionsInDays.toLong()) + }, + + plausibleDeniabilityParameters = if (common.hasPlausibleDeniabilityParameters()) { + SrsPlausibleDeniabilityParameters( + minRequestPaddingBytes = common.plausibleDeniabilityParameters.minRequestPaddingBytes, + maxRequestPaddingBytes = common.plausibleDeniabilityParameters.maxRequestPaddingBytes + ) + } else { + Timber.d("No plausibleDeniabilityParameters -> set to default") + SrsPlausibleDeniabilityParameters() } ) } else { diff --git a/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_srs_parameters.proto b/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_srs_parameters.proto index c13879a343c..8639db9b2c0 100644 --- a/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_srs_parameters.proto +++ b/Server-Protocol-Buffer/src/main/proto/internal/v2/ppdd_srs_parameters.proto @@ -17,4 +17,10 @@ message PPDDSelfReportSubmissionParametersAndroid { message PPDDSelfReportSubmissionParametersCommon { int32 timeSinceOnboardingInHours = 1; int32 timeBetweenSubmissionsInDays = 2; + PPDDSelfReportSubmissionPlausibleDeniabilityParameters plausibleDeniabilityParameters = 3; +} + +message PPDDSelfReportSubmissionPlausibleDeniabilityParameters { + int32 minRequestPaddingBytes = 1; + int32 maxRequestPaddingBytes = 2; } \ No newline at end of file From 8e32d71385a2388dae1e5c833263f6d8542029df Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli Date: Wed, 16 Nov 2022 12:25:14 +0100 Subject: [PATCH 16/16] Log --- .../rki/coronawarnapp/datadonation/analytics/Analytics.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt index 17dbcc181d3..564f0490d5a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt @@ -166,8 +166,11 @@ class Analytics @Inject constructor( .analytics .plausibleDeniabilityParameters .probabilityOfFakeKeySubmission - if (randomSource.nextDouble() <= probability) { - Timber.tag(TAG).d("fake key submission") + + val randomDouble = randomSource.nextDouble() + Timber.tag(TAG).d("randomDouble=%s, probability=%s", randomDouble, probability) + if (randomDouble <= probability) { + Timber.tag(TAG).d("Send fake key submission") playbook.submitFake() } else { Timber.tag(TAG).d("Skip fake key submission")