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

Fix: srs error(EXPOSURE-APP 14395) #5753

Merged
merged 14 commits into from
Dec 13, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor(
srsSubmissionRepository.submit(type = SrsSubmissionType.SRS_SELF_TEST)
srsSubmissionResult.postValue(Success)
} catch (e: Exception) {
Timber.e(e, "submit()")
when (e) {
is SrsSubmissionTruncatedException -> srsSubmissionResult.postValue(TruncatedSubmission(e.message))
else -> srsSubmissionResult.postValue(Error(e))
}
Timber.e(e, "submit()")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package de.rki.coronawarnapp.http
import dagger.Module
import dagger.Provides
import dagger.Reusable
import de.rki.coronawarnapp.BuildConfig
import de.rki.coronawarnapp.http.config.HTTPVariables
import de.rki.coronawarnapp.http.interceptor.RetryInterceptor
import de.rki.coronawarnapp.http.interceptor.WebSecurityVerificationInterceptor
Expand All @@ -29,7 +28,7 @@ class HttpModule {
val interceptors: List<Interceptor> = listOf(
WebSecurityVerificationInterceptor(),
HttpLoggingInterceptor { message -> Timber.tag("OkHttp").v(message) }.apply {
if (BuildConfig.DEBUG) setLevel(HttpLoggingInterceptor.Level.BODY)
setLevel(HttpLoggingInterceptor.Level.BODY)
},
RetryInterceptor(),
HttpErrorParser()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import dagger.Provides
import de.rki.coronawarnapp.environment.datadonation.DataDonationCDNHttpClient
import de.rki.coronawarnapp.environment.datadonation.DataDonationCDNServerUrl
import de.rki.coronawarnapp.environment.submission.SubmissionCDNServerUrl
import de.rki.coronawarnapp.http.HttpErrorParser
import de.rki.coronawarnapp.srs.core.server.SrsAuthorizationApi
import de.rki.coronawarnapp.srs.core.server.SrsSubmissionApi
import de.rki.coronawarnapp.submission.DEFAULT_CACHE_SIZE
Expand Down Expand Up @@ -37,7 +38,11 @@ object SrsSubmissionModule {
@DataDonationCDNServerUrl url: String,
protoConverterFactory: ProtoConverterFactory
): SrsAuthorizationApi = Retrofit.Builder()
.client(client.newBuilder().build())
.client(
client.newBuilder().apply {
interceptors().removeAll { it is HttpErrorParser }
}.build()
)
.baseUrl(url)
.addConverterFactory(protoConverterFactory)
.build()
Expand All @@ -52,7 +57,10 @@ object SrsSubmissionModule {
protoConverterFactory: ProtoConverterFactory,
): SrsSubmissionApi {
val cache = Cache(File(context.cacheDir, "http_submission"), DEFAULT_CACHE_SIZE)
val cachingClient = client.newBuilder().apply { cache(cache) }.build()
val cachingClient = client.newBuilder().apply {
interceptors().removeAll { it is HttpErrorParser }
cache(cache)
}.build()

return Retrofit.Builder()
.client(cachingClient)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.srs.core.repository

import androidx.annotation.VisibleForTesting
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import com.google.protobuf.ByteString
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.appconfig.ConfigData
import de.rki.coronawarnapp.appconfig.getSupportedCountries
Expand All @@ -12,7 +13,7 @@ import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException
import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository
import de.rki.coronawarnapp.presencetracing.checkins.CheckInsTransformer
import de.rki.coronawarnapp.presencetracing.checkins.common.completedCheckIns
import de.rki.coronawarnapp.server.protocols.internal.ppdd.SrsOtp.SRSOneTimePassword
import de.rki.coronawarnapp.server.protocols.internal.ppdd.SrsOtpRequestAndroid.SRSOneTimePasswordRequestAndroid
import de.rki.coronawarnapp.srs.core.AndroidIdProvider
import de.rki.coronawarnapp.srs.core.SubmissionReporter
import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException
Expand All @@ -23,6 +24,7 @@ import de.rki.coronawarnapp.srs.core.model.SrsOtp
import de.rki.coronawarnapp.srs.core.model.SrsSubmissionPayload
import de.rki.coronawarnapp.srs.core.model.SrsSubmissionType
import de.rki.coronawarnapp.srs.core.playbook.SrsPlaybook
import de.rki.coronawarnapp.srs.core.server.errorArgs
import de.rki.coronawarnapp.srs.core.storage.SrsDevSettings
import de.rki.coronawarnapp.srs.core.storage.SrsSubmissionSettings
import de.rki.coronawarnapp.submission.Symptoms
Expand All @@ -31,6 +33,7 @@ import de.rki.coronawarnapp.submission.task.ExposureKeyHistoryCalculations
import de.rki.coronawarnapp.tag
import de.rki.coronawarnapp.util.TimeStamper
import kotlinx.coroutines.flow.first
import okio.ByteString.Companion.toByteString
import timber.log.Timber
import java.time.Instant
import javax.inject.Inject
Expand Down Expand Up @@ -58,14 +61,20 @@ class SrsSubmissionRepository @Inject constructor(
val appConfig = appConfigProvider.getAppConfig()
val nowUtc = timeStamper.nowUTC
var srsOtp = currentOtp(nowUtc).also { OtpCensor.otp = it }
val attestResult = attest(appConfig, srsOtp, srsDevSettings.checkLocalPrerequisites())
val androidId = androidIdProvider.getAndroidId()
val attestResult = attest(
appConfig = appConfig,
srsOtp = srsOtp,
androidId = androidId,
checkDeviceTime = srsDevSettings.checkLocalPrerequisites()
)

if (srsOtp.isValid(nowUtc)) {
Timber.d("Otp is still valid -> fakePlaybookAuthorization")
playbook.fakeAuthorize(
SrsAuthorizationFakeRequest(
safetyNetJws = attestResult.report.jwsResult,
salt = String(attestResult.ourSalt),
salt = attestResult.ourSalt.toByteString().base64(),
)
)
} else {
Expand All @@ -74,8 +83,8 @@ class SrsSubmissionRepository @Inject constructor(
SrsAuthorizationRequest(
srsOtp = srsOtp,
safetyNetJws = attestResult.report.jwsResult,
salt = String(attestResult.ourSalt),
androidId = androidIdProvider.getAndroidId()
salt = attestResult.ourSalt.toByteString().base64(),
androidId = androidId
)
)
srsOtp = srsOtp.copy(expiresAt = expiresAt)
Expand Down Expand Up @@ -133,20 +142,33 @@ class SrsSubmissionRepository @Inject constructor(
internal suspend fun attest(
appConfig: ConfigData,
srsOtp: SrsOtp,
androidId: ByteString,
checkDeviceTime: Boolean = true
): AttestationContainer = try {
val attestRequest = SrsDeviceAttestationRequest(
configData = appConfig,
checkDeviceTime = checkDeviceTime,
scenarioPayload = SRSOneTimePassword.newBuilder().setOtp(srsOtp.uuid.toString()).build().toByteArray()
scenarioPayload = SRSOneTimePasswordRequestAndroid.SRSOneTimePassword.newBuilder()
.setOtp(srsOtp.uuid.toString())
.setAndroidId(androidId)
.build()
.toByteArray()
)
val attestResult = deviceAttestation.attest(attestRequest) as AttestationContainer
attestResult.requirePass(appConfig.selfReportSubmission.ppac)
attestResult
} catch (e: Exception) {
Timber.d(e, "attest() failed -> map to SRS error")
throw when (e) {
is SafetyNetException -> SrsSubmissionException(errorCode = e.type.toSrsErrorType(), cause = e)
is SafetyNetException -> {
val errorCode = e.type.toSrsErrorType()
SrsSubmissionException(
errorCode = errorCode,
errorArgs = errorCode.errorArgs(appConfig.selfReportSubmission),
cause = e
)
}

else -> e
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import javax.inject.Inject
import dagger.Lazy
import dagger.Reusable
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig
import de.rki.coronawarnapp.exception.http.CwaUnknownHostException
import de.rki.coronawarnapp.exception.http.NetworkConnectTimeoutException
import de.rki.coronawarnapp.exception.http.NetworkReadTimeoutException
Expand Down Expand Up @@ -113,12 +114,13 @@ class SrsAuthorizationServer @Inject constructor(
}
val bodyResponse = api.authenticate(headers, srsOtpRequest)
val response = bodyResponse.body()?.charStream()?.use { mapper.readValue<SrsAuthorizationResponse>(it) }
val serverErrorCode = getServerErrorCode(bodyResponse)
return when {
response?.errorCode != null -> {
val errorCode = ErrorCode.fromAuthErrorCode(response.errorCode)
serverErrorCode != null -> {
val errorCode = ErrorCode.fromAuthErrorCode(serverErrorCode)
throw SrsSubmissionException(
errorCode = errorCode,
errorArgs = errorArgs(errorCode)
errorArgs = errorCode.errorArgs(appConfigProvider.currentConfig.first().selfReportSubmission)
)
}

Expand All @@ -134,33 +136,35 @@ class SrsAuthorizationServer @Inject constructor(
}
}

private suspend fun errorArgs(errorCode: ErrorCode): Array<Any> = when (errorCode) {
ErrorCode.SUBMISSION_TOO_EARLY -> arrayOf(
appConfigProvider
.currentConfig
.first()
.selfReportSubmission
.common
.timeBetweenSubmissionsInDays
.toDays()
)

ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED,
ErrorCode.MIN_TIME_SINCE_ONBOARDING -> {
val hours = appConfigProvider
.currentConfig
.first()
.selfReportSubmission
.common
.timeSinceOnboardingInHours
.toHours()
arrayOf(hours, hours)
}

else -> emptyArray()
}
private fun getServerErrorCode(bodyResponse: Response<ResponseBody>): String? = runCatching {
bodyResponse.errorBody()?.charStream()?.use {
mapper.readValue<SrsAuthorizationResponse>(it)
}?.errorCode
}.onFailure {
Timber.d(it, "getServerErrorCode()")
}.getOrNull()

companion object {
val TAG = tag<SrsAuthorizationServer>()
}
}

fun ErrorCode.errorArgs(selfReportSubmission: SelfReportSubmissionConfig): Array<Any> = when (this) {
ErrorCode.SUBMISSION_TOO_EARLY -> arrayOf(
selfReportSubmission
.common
.timeBetweenSubmissionsInDays
.toDays()
)

ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED,
ErrorCode.MIN_TIME_SINCE_ONBOARDING -> {
val hours = selfReportSubmission
.common
.timeSinceOnboardingInHours
.toHours()
arrayOf(hours, hours)
}

else -> emptyArray()
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import retrofit2.http.POST
interface SrsSubmissionApi {
@Throws(CwaWebException::class)
@POST("version/v1/diagnosis-keys")
@Headers("Content-Type: application/x-protobuf")
@Headers("Content-Type: application/x-protobuf", "cwa-fake: 0")
suspend fun submitPayload(
@Header("cwa-otp") otp: String,
@Body requestBody: SubmissionPayloadOuterClass.SubmissionPayload
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class SrsSymptomsCalendarViewModel @AssistedInject constructor(
)
events.postValue(SrsSymptomsCalendarNavigation.GoToThankYouScreen(submissionType))
} catch (e: Exception) {
Timber.e(e, "submitSrs()")
when (e) {
is SrsSubmissionTruncatedException -> events.postValue(
SrsSymptomsCalendarNavigation.TruncatedSubmission(e.message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class SrsSymptomsIntroductionViewModel @AssistedInject constructor(
)
events.postValue(SrsSymptomsIntroductionNavigation.GoToThankYouScreen(submissionType))
} catch (e: Exception) {
Timber.e(e, "submitSrs()")
when (e) {
is SrsSubmissionTruncatedException -> events.postValue(
SrsSymptomsIntroductionNavigation.TruncatedSubmission(e.message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {

@Test
fun `attest pass`() = runTest {
instance().attest(configData, srsOtp) shouldBe attestationContainer
instance().attest(configData, srsOtp, ByteString.EMPTY) shouldBe attestationContainer
}

@Test
Expand All @@ -241,7 +241,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {
SafetyNetException.Type.PLAY_SERVICES_VERSION_MISMATCH
)
shouldThrow<SrsSubmissionException> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.errorCode shouldBe SrsSubmissionException.ErrorCode.PLAY_SERVICES_VERSION_MISMATCH
}

Expand All @@ -251,7 +251,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {
SafetyNetException.Type.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED
)
shouldThrow<SrsSubmissionException> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.errorCode shouldBe SrsSubmissionException.ErrorCode.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED
}

Expand All @@ -261,7 +261,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {
SafetyNetException.Type.EVALUATION_TYPE_BASIC_REQUIRED
)
shouldThrow<SrsSubmissionException> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.errorCode shouldBe SrsSubmissionException.ErrorCode.EVALUATION_TYPE_BASIC_REQUIRED
}

Expand All @@ -271,7 +271,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {
SafetyNetException.Type.APK_PACKAGE_NAME_MISMATCH
)
shouldThrow<SrsSubmissionException> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.errorCode shouldBe SrsSubmissionException.ErrorCode.APK_PACKAGE_NAME_MISMATCH
}

Expand All @@ -281,7 +281,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {
SafetyNetException.Type.ATTESTATION_FAILED
)
shouldThrow<SrsSubmissionException> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.errorCode shouldBe SrsSubmissionException.ErrorCode.ATTESTATION_FAILED
}

Expand All @@ -291,7 +291,7 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {
SafetyNetException.Type.INTERNAL_ERROR
)
shouldThrow<SrsSubmissionException> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.errorCode shouldBe SrsSubmissionException.ErrorCode.ATTESTATION_FAILED
}

Expand All @@ -301,15 +301,15 @@ internal class SrsSubmissionRepositoryTest : BaseTest() {
SafetyNetException.Type.ATTESTATION_REQUEST_FAILED
)
shouldThrow<SrsSubmissionException> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.errorCode shouldBe SrsSubmissionException.ErrorCode.ATTESTATION_REQUEST_FAILED
}

@Test
fun `attest - other error`() = runTest {
coEvery { deviceAttestation.attest(any()) } throws Exception("Surprise!")
shouldThrow<Exception> {
instance().attest(configData, srsOtp)
instance().attest(configData, srsOtp, ByteString.EMPTY)
}.message shouldBe "Surprise!"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ internal class SrsAuthorizationServerTest : BaseTest() {
// iOS error that does not map to Android Error :D
"API_TOKEN_ALREADY_ISSUED" to ErrorCode.SRS_OTP_SERVER_ERROR,
).forEach { (serverErrorCode, errorCode) ->
coEvery { srsAuthorizationApi.authenticate(any(), any()) } returns Response.success(
coEvery { srsAuthorizationApi.authenticate(any(), any()) } returns Response.error(
400,
"""
{
"errorCode": "$serverErrorCode"
Expand Down