Skip to content
Snippets Groups Projects
Unverified Commit f98ac2dd authored by Fabian-K's avatar Fabian-K Committed by GitHub
Browse files

plausible deniability (#946)

* Added requestPadding property to all VerficationService requests

* Added fake calls to the WebReqeustBuilder

* :construction:

 initial draft to enable plausible deniability

* Switched from SubmissionConstants to KeyType enum everywhere

* basic playbook implementation with fake and real requests

* Playbook
- ensure request pattern for playbooks is always the same

VerificationService
- apply padding to ensure equal request size (header & body)

SecurityHelper
- extract hash256 to HashHelper. This simplifies tests that use only the hash function (and therefore don´t need to initialize SecurityHelper and its dependencies)

* Implemented random chance of dummy playbook execution on app open

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Playbook
- ignore exceptions for fake requests

SubmissionService
- add padding header to fake request for same header size

WebRequestBuilder
- include fake keys in upload (:construction_site:

)

* DiagnosisKeyService: removed (low value & difficult to test)

SubmissionService & SubmitDiagnosisKeysTransaction
- inline playbook & backgroundNoise property to prevent issues during testing

DiagnosisKeyConstantsTest, SubmissionServiceTest, SubmitDiagnosisKeysTransactionTest & SubmissionViewModelTest
- adjusted to changes

* Dummy playbook will now be repeated and delayed randomly

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Linting

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Initial Code for background noise worker

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* First implementation of noise background worker

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Linting

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* PlaybookImpl
- ensure that fake requests are executed when real requests fail

SubmissionService & VerificationService
- adjust header name for padding

WebRequestBuilder
- add padding to dummy submission

* BackgroundNoise is now trigger-only

PlaybookImpl
- include follow-up executions after every playbook
- logging

SubmissionViewModel.kt, SubmissionService.kt, SubmitDiagnosisKeysTransaction.kt, MainActivity.kt, BackgroundNoisePeriodicWorker.kt, DiagnosisTestResultRetrievalPeriodicWorker.kt
- propagate context for coroutine

VerificationService
- ensure body size of 1000

* WebRequestBuilder.kt
- adjust fake key generation

PlaybookImplTest.kt
- remove unused server.enqueue

SubmissionService.kt 6 SubmitDiagnosisKeysTransaction.kt
- remove commented out code

* revert temporary changes to SubmissionResultPositiveOtherWarningFragment.kt

* Background job scheduling implemented

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* - adjust fake key size
- remove temporary comment

* Moved build work calls to own file to fix linting

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* - initialize coroutine scope within the playbook, revert passing it from outside
- remove experimental test dependency for coroutines

* - use single endpoint per server for fake requests
- reduce request size from 1000 to 250 for the verification server
- include dummy registration token in fake request to fulfill verification on server side
- prepare for randomized count of submitted keys
- always include headers cwa-authorization & cwa-header-padding for submission server

* - simplify empty header using constant

Co-authored-by: default avatarKolya Opahle <k.opahle@sap.com>
parent 53e457f0
No related branches found
No related tags found
No related merge requests found
Showing
with 464 additions and 122 deletions
......@@ -249,6 +249,7 @@ dependencies {
exclude group: 'com.google.protobuf'
}
testImplementation "io.mockk:mockk:1.10.0"
testImplementation "com.squareup.okhttp3:mockwebserver:4.8.0"
testImplementation 'org.hamcrest:hamcrest-library:2.2'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
......
......@@ -20,22 +20,24 @@
package de.rki.coronawarnapp.http
import KeyExportFormat
import com.google.protobuf.ByteString
import com.google.protobuf.InvalidProtocolBufferException
import de.rki.coronawarnapp.exception.ApplicationConfigurationCorruptException
import de.rki.coronawarnapp.exception.ApplicationConfigurationInvalidException
import de.rki.coronawarnapp.http.requests.RegistrationTokenRequest
import de.rki.coronawarnapp.http.requests.RegistrationRequest
import de.rki.coronawarnapp.http.requests.RegistrationTokenRequest
import de.rki.coronawarnapp.http.requests.TanRequestBody
import de.rki.coronawarnapp.http.service.DistributionService
import de.rki.coronawarnapp.http.service.SubmissionService
import de.rki.coronawarnapp.http.service.VerificationService
import de.rki.coronawarnapp.server.protocols.ApplicationConfigurationOuterClass.ApplicationConfiguration
import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyConstants
import de.rki.coronawarnapp.service.submission.KeyType
import de.rki.coronawarnapp.service.submission.SubmissionConstants
import de.rki.coronawarnapp.storage.FileStorageHelper
import de.rki.coronawarnapp.util.TimeAndDateExtensions.toServerFormat
import de.rki.coronawarnapp.util.ZipHelper.unzip
import de.rki.coronawarnapp.util.security.SecurityHelper
import de.rki.coronawarnapp.util.security.HashHelper
import de.rki.coronawarnapp.util.security.VerificationKeys
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
......@@ -43,6 +45,7 @@ import timber.log.Timber
import java.io.File
import java.util.Date
import java.util.UUID
import kotlin.math.max
class WebRequestBuilder(
private val distributionService: DistributionService,
......@@ -136,17 +139,24 @@ class WebRequestBuilder(
suspend fun asyncGetRegistrationToken(
key: String,
keyType: String
keyType: KeyType
): String = withContext(Dispatchers.IO) {
val keyStr = if (keyType == SubmissionConstants.QR_CODE_KEY_TYPE) {
SecurityHelper.hash256(key)
val keyStr = if (keyType == KeyType.GUID) {
HashHelper.hash256(key)
} else {
key
}
val paddingLength = when (keyType) {
KeyType.GUID -> SubmissionConstants.PADDING_LENGTH_BODY_REGISTRATION_TOKEN_GUID
KeyType.TELETAN -> SubmissionConstants.PADDING_LENGTH_BODY_REGISTRATION_TOKEN_TELETAN
}
verificationService.getRegistrationToken(
SubmissionConstants.REGISTRATION_TOKEN_URL,
"0",
RegistrationTokenRequest(keyType, keyStr)
requestPadding(SubmissionConstants.PADDING_LENGTH_HEADER_REGISTRATION_TOKEN),
RegistrationTokenRequest(keyType.name, keyStr, requestPadding(paddingLength))
).registrationToken
}
......@@ -155,7 +165,12 @@ class WebRequestBuilder(
): Int = withContext(Dispatchers.IO) {
verificationService.getTestResult(
SubmissionConstants.TEST_RESULT_URL,
"0", RegistrationRequest(registrationToken)
"0",
requestPadding(SubmissionConstants.PADDING_LENGTH_HEADER_TEST_RESULT),
RegistrationRequest(
registrationToken,
requestPadding(SubmissionConstants.PADDING_LENGTH_BODY_TEST_RESULT)
)
).testResult
}
......@@ -163,30 +178,78 @@ class WebRequestBuilder(
registrationToken: String
): String = withContext(Dispatchers.IO) {
verificationService.getTAN(
SubmissionConstants.TAN_REQUEST_URL, "0",
SubmissionConstants.TAN_REQUEST_URL,
"0",
requestPadding(SubmissionConstants.PADDING_LENGTH_HEADER_TAN),
TanRequestBody(
registrationToken
registrationToken,
requestPadding(SubmissionConstants.PADDING_LENGTH_BODY_TAN)
)
).tan
}
suspend fun asyncFakeVerification() = withContext(Dispatchers.IO) {
verificationService.getTAN(
SubmissionConstants.TAN_REQUEST_URL,
"1",
requestPadding(SubmissionConstants.PADDING_LENGTH_HEADER_TAN),
TanRequestBody(
registrationToken = SubmissionConstants.DUMMY_REGISTRATION_TOKEN,
requestPadding = requestPadding(SubmissionConstants.PADDING_LENGTH_BODY_TAN_FAKE)
)
)
}
suspend fun asyncSubmitKeysToServer(
authCode: String,
faked: Boolean,
keyList: List<KeyExportFormat.TemporaryExposureKey>
) = withContext(Dispatchers.IO) {
Timber.d("Writing ${keyList.size} Keys to the Submission Payload.")
val randomAdditions = 0 // prepare for random addition of keys
val fakeKeyCount =
max(SubmissionConstants.minKeyCountForSubmission + randomAdditions - keyList.size, 0)
val fakeKeyPadding = requestPadding(SubmissionConstants.fakeKeySize * fakeKeyCount)
val submissionPayload = KeyExportFormat.SubmissionPayload.newBuilder()
.addAllKeys(keyList)
.setPadding(ByteString.copyFromUtf8(fakeKeyPadding))
.build()
var fakeHeader = "0"
if (faked) fakeHeader = Math.random().toInt().toString()
submissionService.submitKeys(
DiagnosisKeyConstants.DIAGNOSIS_KEYS_SUBMISSION_URL,
authCode,
fakeHeader,
"0",
SubmissionConstants.EMPTY_HEADER,
submissionPayload
)
return@withContext
}
suspend fun asyncFakeSubmission() = withContext(Dispatchers.IO) {
val randomAdditions = 0 // prepare for random addition of keys
val fakeKeyCount = SubmissionConstants.minKeyCountForSubmission + randomAdditions
val fakeKeyPadding =
requestPadding(SubmissionConstants.fakeKeySize * fakeKeyCount)
val submissionPayload = KeyExportFormat.SubmissionPayload.newBuilder()
.setPadding(ByteString.copyFromUtf8(fakeKeyPadding))
.build()
submissionService.submitKeys(
DiagnosisKeyConstants.DIAGNOSIS_KEYS_SUBMISSION_URL,
SubmissionConstants.EMPTY_HEADER,
"1",
requestPadding(SubmissionConstants.PADDING_LENGTH_HEADER_SUBMISSION_FAKE),
submissionPayload
)
}
private fun requestPadding(length: Int): String {
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
return (1..length)
.map { allowedChars.random() }
.joinToString("")
}
}
package de.rki.coronawarnapp.http.playbook
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.service.submission.SubmissionConstants
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlin.random.Random
class BackgroundNoise {
companion object {
@Volatile
private var instance: BackgroundNoise? = null
fun getInstance(): BackgroundNoise {
return instance ?: synchronized(this) {
instance ?: BackgroundNoise().also {
instance = it
}
}
}
}
fun scheduleDummyPattern() {
BackgroundWorkScheduler.scheduleBackgroundNoisePeriodicWork()
}
suspend fun foregroundScheduleCheck() {
if (LocalData.isAllowedToSubmitDiagnosisKeys() == true) {
val chance = Random.nextFloat() * 100
if (chance < SubmissionConstants.probabilityToExecutePlaybookWhenOpenApp) {
PlaybookImpl(WebRequestBuilder.getInstance())
.dummy()
}
}
}
}
package de.rki.coronawarnapp.http.playbook
import KeyExportFormat
import de.rki.coronawarnapp.service.submission.KeyType
import de.rki.coronawarnapp.util.formatter.TestResult
interface Playbook {
suspend fun initialRegistration(
key: String,
keyType: KeyType
): String /* registration token */
suspend fun testResult(
registrationToken: String
): TestResult
suspend fun submission(
registrationToken: String,
keys: List<KeyExportFormat.TemporaryExposureKey>
)
suspend fun dummy()
}
package de.rki.coronawarnapp.http.playbook
import KeyExportFormat
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.service.submission.KeyType
import de.rki.coronawarnapp.service.submission.SubmissionConstants
import de.rki.coronawarnapp.util.formatter.TestResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.random.Random
class PlaybookImpl(
private val webRequestBuilder: WebRequestBuilder
) : Playbook {
private val uid = UUID.randomUUID().toString()
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
override suspend fun initialRegistration(key: String, keyType: KeyType): String {
Timber.i("[$uid] New Initial Registration Playbook")
// real registration
val (registrationToken, exception) =
executeCapturingExceptions { webRequestBuilder.asyncGetRegistrationToken(key, keyType) }
// fake verification
ignoreExceptions { webRequestBuilder.asyncFakeVerification() }
// fake submission
ignoreExceptions { webRequestBuilder.asyncFakeSubmission() }
coroutineScope.launch { followUpPlaybooks() }
return registrationToken ?: propagateException(exception)
}
override suspend fun testResult(registrationToken: String): TestResult {
Timber.i("[$uid] New Test Result Playbook")
// real test result
val (testResult, exception) =
executeCapturingExceptions { webRequestBuilder.asyncGetTestResult(registrationToken) }
// fake verification
ignoreExceptions { webRequestBuilder.asyncFakeVerification() }
// fake submission
ignoreExceptions { webRequestBuilder.asyncFakeSubmission() }
coroutineScope.launch { followUpPlaybooks() }
return testResult?.let { TestResult.fromInt(it) }
?: propagateException(exception)
}
override suspend fun submission(
registrationToken: String,
keys: List<KeyExportFormat.TemporaryExposureKey>
) {
Timber.i("[$uid] New Submission Playbook")
// real auth code
val (authCode, exception) = executeCapturingExceptions {
webRequestBuilder.asyncGetTan(
registrationToken
)
}
// fake verification
ignoreExceptions { webRequestBuilder.asyncFakeVerification() }
// real submission
if (authCode != null) {
webRequestBuilder.asyncSubmitKeysToServer(authCode, keys)
coroutineScope.launch { followUpPlaybooks() }
} else {
webRequestBuilder.asyncFakeSubmission()
coroutineScope.launch { followUpPlaybooks() }
propagateException(exception)
}
}
private suspend fun dummy(launchFollowUp: Boolean) {
// fake verification
ignoreExceptions { webRequestBuilder.asyncFakeVerification() }
// fake verification
ignoreExceptions { webRequestBuilder.asyncFakeVerification() }
// fake submission
ignoreExceptions { webRequestBuilder.asyncFakeSubmission() }
if (launchFollowUp)
coroutineScope.launch { followUpPlaybooks() }
}
override suspend fun dummy() = dummy(true)
private suspend fun followUpPlaybooks() {
val runsToExecute = Random.nextInt(
SubmissionConstants.minNumberOfSequentialPlaybooks,
SubmissionConstants.maxNumberOfSequentialPlaybooks
)
Timber.i("[$uid] Follow Up: launching $runsToExecute follow up playbooks")
repeat(runsToExecute) {
val executionDelay = Random.nextInt(
SubmissionConstants.minDelayBetweenSequentialPlaybooks,
SubmissionConstants.maxDelayBetweenSequentialPlaybooks
)
Timber.i("[$uid] Follow Up: (${it + 1}/$runsToExecute) waiting $executionDelay[s]...")
delay(TimeUnit.SECONDS.toMillis(executionDelay.toLong()))
dummy(false)
}
Timber.i("[$uid] Follow Up: finished")
}
private suspend fun ignoreExceptions(body: suspend () -> Unit) {
try {
body.invoke()
} catch (e: Exception) {
Timber.d(e, "Ignoring dummy request exception")
}
}
private suspend fun <T> executeCapturingExceptions(body: suspend () -> T): Pair<T?, Exception?> {
return try {
val result = body.invoke()
result to null
} catch (e: Exception) {
null to e
}
}
private fun propagateException(exception: Exception?): Nothing {
throw exception ?: IllegalStateException()
}
}
......@@ -4,5 +4,7 @@ import com.google.gson.annotations.SerializedName
data class RegistrationRequest(
@SerializedName("registrationToken")
val registrationToken: String
val registrationToken: String? = null,
@SerializedName("requestPadding")
val requestPadding: String? = null
)
......@@ -4,7 +4,9 @@ import com.google.gson.annotations.SerializedName
data class RegistrationTokenRequest(
@SerializedName("keyType")
val keyType: String,
val keyType: String? = null,
@SerializedName("key")
val key: String
val key: String? = null,
@SerializedName("requestPadding")
val requestPadding: String? = null
)
......@@ -4,5 +4,7 @@ import com.google.gson.annotations.SerializedName
data class TanRequestBody(
@SerializedName("registrationToken")
val registrationToken: String
val registrationToken: String? = null,
@SerializedName("requestPadding")
val requestPadding: String? = null
)
......@@ -11,8 +11,9 @@ interface SubmissionService {
@POST
suspend fun submitKeys(
@Url url: String,
@Header("cwa-authorization") authCode: String,
@Header("cwa-authorization") authCode: String?,
@Header("cwa-fake") fake: String,
@Header("cwa-header-padding") headerPadding: String?,
@Body requestBody: KeyExportFormat.SubmissionPayload
): ResponseBody
}
......@@ -17,6 +17,7 @@ interface VerificationService {
suspend fun getRegistrationToken(
@Url url: String,
@Header("cwa-fake") fake: String,
@Header("cwa-header-padding") headerPadding: String?,
@Body requestBody: RegistrationTokenRequest
): RegistrationTokenResponse
......@@ -24,6 +25,7 @@ interface VerificationService {
suspend fun getTestResult(
@Url url: String,
@Header("cwa-fake") fake: String,
@Header("cwa-header-padding") headerPadding: String?,
@Body request: RegistrationRequest
): TestResultResponse
......@@ -31,6 +33,7 @@ interface VerificationService {
suspend fun getTAN(
@Url url: String,
@Header("cwa-fake") fake: String,
@Header("cwa-header-padding") headerPadding: String?,
@Body requestBody: TanRequestBody
): TanResponse
}
......@@ -20,9 +20,7 @@
package de.rki.coronawarnapp.service.diagnosiskey
/**
* The Diagnosis Key constants are used inside the DiagnosisKeyService
*
* @see DiagnosisKeyService
* The Diagnosis Key constants
*/
object DiagnosisKeyConstants {
/** version resource variable for REST-like Service Calls */
......@@ -51,10 +49,10 @@ object DiagnosisKeyConstants {
private var CURRENT_COUNTRY = "DE"
/** Distribution URL built from CDN URL's and REST resources */
private var VERSIONED_DISTRIBUTION_CDN_URL = "/$VERSION/$CURRENT_VERSION"
private var VERSIONED_DISTRIBUTION_CDN_URL = "$VERSION/$CURRENT_VERSION"
/** Submission URL built from CDN URL's and REST resources */
private var VERSIONED_SUBMISSION_CDN_URL = "/$VERSION/$CURRENT_VERSION"
private var VERSIONED_SUBMISSION_CDN_URL = "$VERSION/$CURRENT_VERSION"
/** Parameter Download URL built from CDN URL's and REST resources */
private val PARAMETERS_DOWNLOAD_URL = "$VERSIONED_DISTRIBUTION_CDN_URL/$PARAMETERS"
......
/******************************************************************************
* Corona-Warn-App *
* *
* SAP SE and all other contributors / *
* copyright owners license this file to you under the Apache *
* License, Version 2.0 (the "License"); you may not use this *
* file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
******************************************************************************/
package de.rki.coronawarnapp.service.diagnosiskey
import KeyExportFormat
import de.rki.coronawarnapp.exception.DiagnosisKeyRetrievalException
import de.rki.coronawarnapp.exception.DiagnosisKeySubmissionException
import de.rki.coronawarnapp.http.WebRequestBuilder
import timber.log.Timber
/**
* The Diagnosis Key Service is used to interact with the Server to submit and retrieve keys through
* predefined structures.
*
* @throws DiagnosisKeyRetrievalException An Exception thrown when an error occurs during Key Retrieval from the Server
* @throws DiagnosisKeySubmissionException An Exception thrown when an error occurs during Key Reporting to the Server
*/
object DiagnosisKeyService {
private val TAG: String? = DiagnosisKeyService::class.simpleName
/**
* Asynchronously submits keys to the Server with the WebRequestBuilder by retrieving
* keys out of the Google API.
*
*
* @throws de.rki.coronawarnapp.exception.DiagnosisKeySubmissionException An Exception thrown when an error occurs during Key Reporting to the Server
*
* @param authCode - TAN Authorization Code used to validate the request
* @param keysToReport - KeyList in the Server Format to submit to the Server
*/
suspend fun asyncSubmitKeys(
authCode: String,
keysToReport: List<KeyExportFormat.TemporaryExposureKey>
) {
Timber.d("Diagnosis Keys will be submitted.")
WebRequestBuilder.getInstance().asyncSubmitKeysToServer(
authCode,
false,
keysToReport
)
}
}
......@@ -10,9 +10,6 @@ object SubmissionConstants {
private val VERSIONED_VERIFICATION_CDN_URL = "$VERSION/$CURRENT_VERSION"
const val QR_CODE_KEY_TYPE = "GUID"
const val TELE_TAN_KEY_TYPE = "TELETAN"
val REGISTRATION_TOKEN_URL = "$VERSIONED_VERIFICATION_CDN_URL/$REGISTRATION_TOKEN"
val TEST_RESULT_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TEST_RESULT"
val TAN_REQUEST_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TAN"
......@@ -22,4 +19,38 @@ object SubmissionConstants {
const val GUID_SEPARATOR = '?'
const val SERVER_ERROR_CODE_400 = 400
const val EMPTY_HEADER = ""
// padding registration token
private const val VERIFICATION_BODY_FILL = 139
const val PADDING_LENGTH_HEADER_REGISTRATION_TOKEN = 0
const val PADDING_LENGTH_BODY_REGISTRATION_TOKEN_TELETAN = 51 + VERIFICATION_BODY_FILL
const val PADDING_LENGTH_BODY_REGISTRATION_TOKEN_GUID = 0 + VERIFICATION_BODY_FILL
// padding test result
const val PADDING_LENGTH_HEADER_TEST_RESULT = 7
const val PADDING_LENGTH_BODY_TEST_RESULT = 31 + VERIFICATION_BODY_FILL
// padding tan
const val PADDING_LENGTH_HEADER_TAN = 14
const val PADDING_LENGTH_BODY_TAN = 31 + VERIFICATION_BODY_FILL
const val PADDING_LENGTH_BODY_TAN_FAKE = 31 + VERIFICATION_BODY_FILL
const val DUMMY_REGISTRATION_TOKEN = "11111111-2222-4444-8888-161616161616"
const val PADDING_LENGTH_HEADER_SUBMISSION_FAKE = 36
const val probabilityToExecutePlaybookWhenOpenApp = 1f
const val minNumberOfSequentialPlaybooks = 1
const val maxNumberOfSequentialPlaybooks = 3
const val minDelayBetweenSequentialPlaybooks = 5
const val maxDelayBetweenSequentialPlaybooks = 10
const val minKeyCountForSubmission = 14
const val fakeKeySize = (1 * 16 /* key data*/) + (3 * 4 /* 3x int32*/)
}
enum class KeyType {
GUID, TELETAN;
}
......@@ -4,14 +4,15 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException
import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.service.submission.SubmissionConstants.QR_CODE_KEY_TYPE
import de.rki.coronawarnapp.service.submission.SubmissionConstants.TELE_TAN_KEY_TYPE
import de.rki.coronawarnapp.http.playbook.BackgroundNoise
import de.rki.coronawarnapp.http.playbook.PlaybookImpl
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction
import de.rki.coronawarnapp.util.formatter.TestResult
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
object SubmissionService {
suspend fun asyncRegisterDevice() {
val testGUID = LocalData.testGUID()
val testTAN = LocalData.teletan()
......@@ -22,13 +23,14 @@ object SubmissionService {
else -> throw NoGUIDOrTANSetException()
}
LocalData.devicePairingSuccessfulTimestamp(System.currentTimeMillis())
BackgroundNoise.getInstance().scheduleDummyPattern()
}
private suspend fun asyncRegisterDeviceViaGUID(guid: String) {
val registrationToken =
WebRequestBuilder.getInstance().asyncGetRegistrationToken(
PlaybookImpl(WebRequestBuilder.getInstance()).initialRegistration(
guid,
QR_CODE_KEY_TYPE
KeyType.GUID
)
LocalData.registrationToken(registrationToken)
......@@ -37,19 +39,15 @@ object SubmissionService {
private suspend fun asyncRegisterDeviceViaTAN(tan: String) {
val registrationToken =
WebRequestBuilder.getInstance().asyncGetRegistrationToken(
PlaybookImpl(WebRequestBuilder.getInstance()).initialRegistration(
tan,
TELE_TAN_KEY_TYPE
KeyType.TELETAN
)
LocalData.registrationToken(registrationToken)
deleteTeleTAN()
}
suspend fun asyncRequestAuthCode(registrationToken: String): String {
return WebRequestBuilder.getInstance().asyncGetTan(registrationToken)
}
suspend fun asyncSubmitExposureKeys(keys: List<TemporaryExposureKey>) {
val registrationToken =
LocalData.registrationToken() ?: throw NoRegistrationTokenSetException()
......@@ -59,9 +57,8 @@ object SubmissionService {
suspend fun asyncRequestTestResult(): TestResult {
val registrationToken =
LocalData.registrationToken() ?: throw NoRegistrationTokenSetException()
return TestResult.fromInt(
WebRequestBuilder.getInstance().asyncGetTestResult(registrationToken)
)
return PlaybookImpl(WebRequestBuilder.getInstance()).testResult(registrationToken)
}
fun containsValidGUID(scanResult: String): Boolean {
......
package de.rki.coronawarnapp.transaction
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyService
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.http.playbook.PlaybookImpl
import de.rki.coronawarnapp.service.submission.SubmissionService
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.CLOSE
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TAN
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TAN_AND_SUBMIT_KEYS
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.STORE_SUCCESS
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.SUBMIT_KEYS
import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.limitKeyCount
import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat
......@@ -41,22 +41,17 @@ object SubmitDiagnosisKeysTransaction : Transaction() {
/** possible transaction states */
private enum class SubmitDiagnosisKeysTransactionState :
TransactionState {
RETRIEVE_TAN,
RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY,
SUBMIT_KEYS,
RETRIEVE_TAN_AND_SUBMIT_KEYS,
STORE_SUCCESS,
CLOSE
}
/** initiates the transaction. This suspend function guarantees a successful transaction once completed. */
suspend fun start(registrationToken: String, keys: List<TemporaryExposureKey>) = lockAndExecuteUnique {
/****************************************************
* RETRIEVE TAN
****************************************************/
val authCode = executeState(RETRIEVE_TAN) {
SubmissionService.asyncRequestAuthCode(registrationToken)
}
suspend fun start(
registrationToken: String,
keys: List<TemporaryExposureKey>
) = lockAndExecuteUnique {
/****************************************************
* RETRIEVE TEMPORARY EXPOSURE KEY HISTORY
****************************************************/
......@@ -65,10 +60,13 @@ object SubmitDiagnosisKeysTransaction : Transaction() {
.transformKeyHistoryToExternalFormat()
}
/****************************************************
* SUBMIT KEYS
* RETRIEVE TAN & SUBMIT KEYS
****************************************************/
executeState(SUBMIT_KEYS) {
DiagnosisKeyService.asyncSubmitKeys(authCode, temporaryExposureKeyList)
executeState(RETRIEVE_TAN_AND_SUBMIT_KEYS) {
PlaybookImpl(WebRequestBuilder.getInstance()).submission(
registrationToken,
temporaryExposureKeyList
)
}
/****************************************************
* STORE SUCCESS
......
......@@ -9,14 +9,17 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.lifecycleScope
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.http.playbook.BackgroundNoise
import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
import de.rki.coronawarnapp.util.ConnectivityHelper
import de.rki.coronawarnapp.util.DialogHelper
import de.rki.coronawarnapp.util.ExternalActionHelper
import de.rki.coronawarnapp.util.PowerManagementHelper
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlinx.coroutines.launch
/**
* This activity holds all the fragments (except onboarding) and also registers a listener for
......@@ -96,6 +99,13 @@ class MainActivity : AppCompatActivity() {
settingsViewModel.updateBackgroundJobEnabled(ConnectivityHelper.autoModeEnabled(this))
scheduleWork()
checkShouldDisplayBackgroundWarning()
doBackgroundNoiseCheck()
}
private fun doBackgroundNoiseCheck() {
lifecycleScope.launch {
BackgroundNoise.getInstance().foregroundScheduleCheck()
}
}
private fun showEnergyOptimizedEnabledForBackground() {
......
package de.rki.coronawarnapp.util.security
import java.security.MessageDigest
object HashHelper {
fun hash256(input: String): String = MessageDigest
.getInstance(SecurityConstants.DIGEST_ALGORITHM)
.digest(input.toByteArray())
.fold("", { str, it -> str + "%02x".format(it) })
}
......@@ -31,9 +31,7 @@ import de.rki.coronawarnapp.exception.CwaSecurityException
import de.rki.coronawarnapp.util.security.SecurityConstants.CWA_APP_SQLITE_DB_PW
import de.rki.coronawarnapp.util.security.SecurityConstants.DB_PASSWORD_MAX_LENGTH
import de.rki.coronawarnapp.util.security.SecurityConstants.DB_PASSWORD_MIN_LENGTH
import de.rki.coronawarnapp.util.security.SecurityConstants.DIGEST_ALGORITHM
import de.rki.coronawarnapp.util.security.SecurityConstants.ENCRYPTED_SHARED_PREFERENCES_FILE
import java.security.MessageDigest
import java.security.SecureRandom
/**
......@@ -110,11 +108,6 @@ object SecurityHelper {
return password
}
fun hash256(input: String): String = MessageDigest
.getInstance(DIGEST_ALGORITHM)
.digest(input.toByteArray())
.fold("", { str, it -> str + "%02x".format(it) })
fun <T> withSecurityCatch(doInCatch: () -> T) = try {
doInCatch.invoke()
} catch (e: Exception) {
......
......@@ -24,6 +24,16 @@ object BackgroundConstants {
*/
const val DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG = "DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER"
/**
* Tag for background noise playbook periodic work
*/
const val BACKGROUND_NOISE_PERIODIC_WORKER_TAG = "BACKGROUND_NOISE_PERIODIC_WORKER"
/**
* Tag for background noise playbook one time work
*/
const val BACKGROUND_NOISE_ONE_TIME_WORKER_TAG = "BACKGROUND_NOISE_PERIODIC_WORKER"
/**
* Unique name for diagnosis key retrieval one time work
*/
......@@ -39,6 +49,16 @@ object BackgroundConstants {
*/
const val DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME = "DiagnosisTestResultBackgroundPeriodicWork"
/**
* Unique name for background noise playbook periodic work
*/
const val BACKGROUND_NOISE_PERIODIC_WORK_NAME = "BackgroundNoisePeriodicWork"
/**
* Unique name for background noise playbook one time work
*/
const val BACKGROUND_NOISE_ONE_TIME_WORK_NAME = "BackgroundNoiseOneTimeWork"
/**
* Total minutes in one day
*/
......@@ -94,4 +114,25 @@ object BackgroundConstants {
* @see TimeUnit.MINUTES
*/
const val BACKOFF_INITIAL_DELAY = 8L
/**
* The minimum time in hours to wait between playbook executions
*
* @see TimeUnit.HOURS
*/
const val MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION = 4L
/**
* The maximum time in hours to wait between playbook executions
*
* @see TimeUnit.HOURS
*/
const val MAX_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION = 12L
/**
* The total time in days to run the playbook
*
* @see TimeUnit.DAYS
*/
const val NUMBER_OF_DAYS_TO_RUN_PLAYBOOK = 16
}
package de.rki.coronawarnapp.worker
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.http.playbook.PlaybookImpl
/**
* One time background noise worker
*
* @see BackgroundWorkScheduler
*/
class BackgroundNoiseOneTimeWorker(
val context: Context,
workerParams: WorkerParameters
) :
CoroutineWorker(context, workerParams) {
companion object {
private val TAG: String? = BackgroundNoiseOneTimeWorker::class.simpleName
}
/**
* Work execution
*
* @return Result
*/
override suspend fun doWork(): Result {
var result = Result.success()
try {
PlaybookImpl(WebRequestBuilder.getInstance())
.dummy()
} catch (e: Exception) {
// TODO: Should we even retry here?
result = if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) {
Result.failure()
} else {
Result.retry()
}
}
return result
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment