Skip to content
Snippets Groups Projects
Unverified Commit 0b978c2a authored by Matthias Urhahn's avatar Matthias Urhahn Committed by GitHub
Browse files

Fix nonce comparison (Google returns the nonce base64 encoded). (#2308)

Add additional test menu options to test requirement validation.
parent d9600c84
No related branches found
No related tags found
No related merge requests found
......@@ -48,6 +48,23 @@ class DataDonationTestFragment : Fragment(R.layout.fragment_test_datadonation),
startActivity(intent)
}
vm.currentValidation.observe2(this) { items ->
if (items?.first == null) {
binding.safetynetRequirementsBody.text = "No validation yet."
return@observe2
}
binding.safetynetRequirementsBody.apply {
text = items.first.toString()
if (items.second != null) {
append("\n\n" + items.second.toString())
}
}
}
binding.apply {
safetynetRequirementsCasually.setOnClickListener { vm.validateSafetyNetCasually() }
safetynetRequirementsStrict.setOnClickListener { vm.validateSafetyNetStrict() }
}
vm.errorEvents.observe2(this) {
Toast.makeText(requireContext(), it.toString(), Toast.LENGTH_LONG).show()
}
......
......@@ -3,6 +3,9 @@ package de.rki.coronawarnapp.test.datadonation.ui
import androidx.lifecycle.asLiveData
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer
import de.rki.coronawarnapp.datadonation.safetynet.CWASafetyNet
import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation
import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetClientWrapper
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.ui.SingleLiveEvent
......@@ -15,11 +18,17 @@ import java.security.SecureRandom
class DataDonationTestFragmentViewModel @AssistedInject constructor(
dispatcherProvider: DispatcherProvider,
private val safetyNetClientWrapper: SafetyNetClientWrapper,
private val secureRandom: SecureRandom
private val secureRandom: SecureRandom,
private val cwaSafetyNet: CWASafetyNet
) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
private val currentReportInternal = MutableStateFlow<SafetyNetClientWrapper.Report?>(null)
val currentReport = currentReportInternal.asLiveData(context = dispatcherProvider.Default)
private val currentValidationInternal =
MutableStateFlow<Pair<SafetyNetRequirementsContainer?, Throwable?>?>(null)
val currentValidation = currentValidationInternal.asLiveData(context = dispatcherProvider.Default)
val errorEvents = SingleLiveEvent<Throwable>()
val copyJWSEvent = SingleLiveEvent<String>()
......@@ -37,6 +46,38 @@ class DataDonationTestFragmentViewModel @AssistedInject constructor(
}
}
fun validateSafetyNetStrict() {
validateRequirements(
SafetyNetRequirementsContainer(
requireBasicIntegrity = true,
requireCTSProfileMatch = true,
requireEvaluationTypeBasic = true,
requireEvaluationTypeHardwareBacked = true
)
)
}
fun validateSafetyNetCasually() {
validateRequirements(SafetyNetRequirementsContainer())
}
private fun validateRequirements(requirements: SafetyNetRequirementsContainer) {
launch {
val payload = ByteArray(16)
secureRandom.nextBytes(payload)
try {
val result = cwaSafetyNet.attest(object : DeviceAttestation.Request {
override val scenarioPayload: ByteArray = payload
})
result.requirePass(requirements)
currentValidationInternal.value = requirements to null
} catch (e: Exception) {
Timber.e(e, "validateRequirements() did not pass.")
currentValidationInternal.value = requirements to e
}
}
}
fun copyJWS() {
launch {
val value = currentReport.value?.jwsResult ?: ""
......
......@@ -16,7 +16,7 @@
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/debug_container"
android:id="@+id/safetynet_container"
style="@style/Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
......@@ -65,6 +65,55 @@
app:layout_constraintTop_toBottomOf="@id/safetynet_body" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/safetynet_requirements_container"
style="@style/Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_tiny">
<TextView
android:id="@+id/safetynet_requirements_title"
style="@style/headline6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="SafetyNet Requirements"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/safetynet_requirements_body"
android:layout_width="match_parent"
android:textIsSelectable="true"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/safetynet_requirements_title"
tools:text="Body" />
<Button
android:id="@+id/safetynet_requirements_strict"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Strict"
app:layout_constraintEnd_toStartOf="@+id/safetynet_requirements_casually"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/safetynet_requirements_body" />
<Button
android:id="@+id/safetynet_requirements_casually"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Casually"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/safetynet_requirements_strict"
app:layout_constraintTop_toBottomOf="@id/safetynet_requirements_body" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
......
......@@ -10,6 +10,7 @@ import de.rki.coronawarnapp.util.HashExtensions.toSHA256
import de.rki.coronawarnapp.util.TimeStamper
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.gplay.GoogleApiVersion
import okio.ByteString.Companion.toByteString
import org.joda.time.Duration
import org.joda.time.Instant
import timber.log.Timber
......@@ -62,7 +63,12 @@ class CWASafetyNet @Inject constructor(
val salt = generateSalt()
val nonce = calculateNonce(salt = salt, payload = request.scenarioPayload)
Timber.tag(TAG).d("With salt=%s and payload=%s, we created nonce=%s", salt, request.scenarioPayload, nonce)
Timber.tag(TAG).d(
"With salt=%s and payload=%s, we created nonce=%s",
salt.toByteString().base64(),
request.scenarioPayload.toByteString().base64(),
nonce
)
val report = client.attest(nonce.toByteArray())
......
......@@ -95,7 +95,7 @@ class SafetyNetClientWrapper @Inject constructor(
val body: JsonObject,
val signature: ByteArray
) {
val nonce: String? = body.get("nonce")?.asString
val nonce: String? = body.get("nonce")?.asString?.decodeBase64()?.utf8()
val apkPackageName: String? = body.get("apkPackageName")?.asString
......@@ -103,7 +103,9 @@ class SafetyNetClientWrapper @Inject constructor(
val ctsProfileMatch = body.get("ctsProfileMatch")?.asBoolean == true
val evaluationTypes = body.get("evaluationType")?.asString
?.split(",")?.map { it.trim() } ?: emptyList()
?.split(",")
?.map { it.trim() }
?: emptyList()
val error: String? = body.get("error")?.asString
val advice: String? = body.get("advice")?.asString
......
......@@ -148,7 +148,7 @@ class SafetyNetClientWrapperTest : BaseTest() {
body shouldBe JsonParser.parseString(JWS_BODY)
signature shouldBe JWS_SIGNATURE_BASE64.decodeBase64()!!.toByteArray()
nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA=="
nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==".decodeBase64()?.utf8()
apkPackageName shouldBe "de.rki.coronawarnapp.test"
basicIntegrity shouldBe false
ctsProfileMatch shouldBe false
......@@ -164,7 +164,7 @@ class SafetyNetClientWrapperTest : BaseTest() {
createInstance().attest("hodl".toByteArray()).apply {
body shouldBe JsonParser.parseString(JWS_BODY_MINIMAL)
nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA=="
nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==".decodeBase64()?.utf8()
apkPackageName shouldBe "de.rki.coronawarnapp.test"
basicIntegrity shouldBe false
ctsProfileMatch shouldBe false
......
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