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

Trace Location URL and ID (EXPOSUREAPP-6137, EXPOSUREAPP-6182) #2737

Merged
merged 48 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
9b0850a
Use config regex
mtwalli Apr 3, 2021
a0ee98d
Refactoring
mtwalli Apr 3, 2021
7a7c61d
Verify functionality
mtwalli Apr 3, 2021
b7598d4
Specific exceptions
mtwalli Apr 3, 2021
d550367
Tests
mtwalli Apr 3, 2021
5d697e7
lint
mtwalli Apr 3, 2021
2c2a9ec
Generate QR Code url
mtwalli Apr 3, 2021
894cf35
Test base32
mtwalli Apr 3, 2021
ec0c317
Generate QrCode from url data
mtwalli Apr 3, 2021
7c04b47
Remove / added by the system
mtwalli Apr 3, 2021
2c0a6c3
Remove prefix
mtwalli Apr 3, 2021
5286ef6
Prepare for location id calculation
mtwalli Apr 4, 2021
bd22fde
Calculate location id
mtwalli Apr 4, 2021
28ab14c
Location Id from trace location and tests split
mtwalli Apr 4, 2021
3de16aa
Display last added location data in test menu
mtwalli Apr 4, 2021
5b3a38f
Update EventRegistrationTestFragmentViewModel.kt
mtwalli Apr 4, 2021
44ab3ae
Simplify
mtwalli Apr 4, 2021
fb9d3a3
Display last locations
mtwalli Apr 4, 2021
8bed18c
Pass check In Id
mtwalli Apr 4, 2021
d36d689
Tweaking
mtwalli Apr 4, 2021
08abf5d
Style text
mtwalli Apr 5, 2021
f1ce073
Base64
mtwalli Apr 5, 2021
10527f0
Refactoring
mtwalli Apr 5, 2021
33338fb
Move location id and url to TraceLocation and create them lazily
mtwalli Apr 5, 2021
c4bad2c
Update ConfirmCheckInViewModelTest.kt
mtwalli Apr 5, 2021
45af8dd
Update test screen
mtwalli Apr 5, 2021
ac32bb2
Base64 Id for readability
mtwalli Apr 5, 2021
28e0f67
Remove added descriptor
mtwalli Apr 5, 2021
615901f
Return null instead of 0
mtwalli Apr 5, 2021
c5aa629
Use Okio extension
mtwalli Apr 5, 2021
bc44252
Add parcelization test
mtwalli Apr 5, 2021
1ef8fa1
Update VerifiedTraceLocationTest.kt
mtwalli Apr 5, 2021
71be2dd
Provide locationId hash
mtwalli Apr 6, 2021
b5d0fa6
Delete DefaultQRCodeVerifierTest2.kt
mtwalli Apr 6, 2021
1645d97
Merge branch 'release/2.0.x' into feature/6137-qr-code-content
harambasicluka Apr 6, 2021
ebee84b
Add traceLocationIdHash to TraceLocation
mtwalli Apr 6, 2021
0b631c4
Rename
mtwalli Apr 6, 2021
cc99574
Merge branch 'release/2.0.x' into feature/6137-qr-code-content
mtwalli Apr 6, 2021
bef8598
Revert , server will match both
mtwalli Apr 6, 2021
1937040
Merge branch 'release/2.0.x' into feature/6137-qr-code-content
mtwalli Apr 6, 2021
d1c97a4
Wrap exceptions
mtwalli Apr 6, 2021
b195f01
Pass cause
mtwalli Apr 6, 2021
19c76a8
lint
mtwalli Apr 6, 2021
1c8e2d8
Return null
mtwalli Apr 6, 2021
a527f27
Create ProtoBufKtTest.kt
mtwalli Apr 6, 2021
c3755df
Merge branch 'release/2.0.x' into feature/6137-qr-code-content
mtwalli Apr 6, 2021
9a61f27
lint
mtwalli Apr 6, 2021
0263ab2
Merge branch 'release/2.0.x' into feature/6137-qr-code-content
d4rken Apr 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,268 +1,79 @@
package de.rki.coronawarnapp.eventregistration.checkins.qrcode

import de.rki.coronawarnapp.environment.EnvironmentSetup
import android.os.Bundle
import android.os.Parcel
import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import okio.ByteString.Companion.decodeBase64
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import testhelpers.BaseTestInstrumentation

@RunWith(JUnit4::class)
class VerifiedTraceLocationTest : BaseTestInstrumentation() {

@MockK lateinit var environmentSetup: EnvironmentSetup

@Before
fun setUp() {
MockKAnnotations.init(this)
every { environmentSetup.appConfigPublicKey } returns PUB_KEY
}

// TODO: Ugly but kinda works
@Test
fun verifyTraceLocationIdGenerationHash1() {
val base64Payload = "CAESLAgBEhFNeSBCaXJ0aGRheSBQYXJ0eRoLYXQgbXkgcGxhY2Uo04ekAT" +
"D3h6QBGmUIARJbMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEst" +
"cUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3" +
"cAfQmxeuFMZAIX2+6A5XhoEMTIzNCIECAEQAg=="
val base64LocationID = "jNcJTCajd9Sen6Tbexl2Yb7O3J7ps47b6k4+QMT4xS0="

val qrCodePayload =
TraceLocationOuterClass.QRCodePayload.parseFrom(base64Payload.decodeBase64()!!.toByteArray())
val instance = VerifiedTraceLocation(qrCodePayload)
fun verifyTraceLocationMapping1() {
val qrCodePayload = TraceLocationOuterClass.QRCodePayload.parseFrom(
BASE64_PAYLOAD_1.decodeBase64()!!.toByteArray()
)

instance.traceLocationID.sha256().base64() shouldBe base64LocationID
VerifiedTraceLocation(qrCodePayload).traceLocation
.apply {
locationId.base64() shouldBe "jNcJTCajd9Sen6Tbexl2Yb7O3J7ps47b6k4+QMT4xS0="
qrCodePayload() shouldBe qrCodePayload
}
}

@Test
fun verifyTraceLocationIdGenerationHash2() {
val base64Payload = "CAESIAgBEg1JY2VjcmVhbSBTaG9wGg1NYWluIFN0cmVldCAxGmUIARJ" +
"bMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT" +
"0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5XhoEMTIzNCIGCAEQARgK"
val base64LocationID = "GMuCjqNmOdYyrFhyvFNTVEeLaZh+uShgUoY0LYJo4YQ="

val qrCodePayload =
TraceLocationOuterClass.QRCodePayload.parseFrom(base64Payload.decodeBase64()!!.toByteArray())
val instance = VerifiedTraceLocation(qrCodePayload)

instance.traceLocationID.sha256().base64() shouldBe base64LocationID
}

/* disabled because of incompatibilities due to latest tech spec changes... needs to be re-written anyway

@Test
fun verifyEventSuccess() = runBlockingTest {
val instant = Instant.ofEpochMilli(2687960 * 1_000L)
shouldNotThrowAny {
val verifyResult = traceLocationQRCodeVerifier.verify(ENCODED_EVENT1.decodeBase32().toByteArray())
verifyResult.apply {
traceLocation.description shouldBe "My Birthday Party"
traceLocation.isBeforeStartTime(instant) shouldBe false
traceLocation.isAfterEndTime(instant) shouldBe false
}
}
}

@Test
fun verifyParcelization() = runBlockingTest {
val verifyResult = traceLocationQRCodeVerifier.verify(ENCODED_EVENT1.decodeBase32().toByteArray())

val expectedTraceLocation = TraceLocation(
guid = "3055331c-2306-43f3-9742-6d8fab54e848",
version = 1,
type = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_OTHER,
description = "My Birthday Party",
address = "at my place",
startDate = Instant.ofEpochSecond(2687955),
endDate = Instant.ofEpochSecond(2687991),
defaultCheckInLengthInMinutes = 0,
byteRepresentation = verifyResult.traceLocationBytes,
signature = verifyResult.signature.toByteArray().toByteString(),
)

verifyResult.traceLocation shouldBe expectedTraceLocation

val bundle = Bundle().apply {
putParcelable("test", verifyResult.traceLocation)
}

val parcelRaw = Parcel.obtain().apply {
writeBundle(bundle)
}.marshall()

val restoredParcel = Parcel.obtain().apply {
unmarshall(parcelRaw, 0, parcelRaw.size)
setDataPosition(0)
}

val restoredData = restoredParcel.readBundle()!!.run {
classLoader = TraceLocation::class.java.classLoader
getParcelable<TraceLocation>("test")
}
restoredData shouldBe expectedTraceLocation
}

@Test
fun verifyEventStartTimeWaning() = runBlockingTest {
val instant = Instant.ofEpochMilli(2687940 * 1_000L)
shouldNotThrowAny {
val verifyResult = traceLocationQRCodeVerifier.verify(ENCODED_EVENT1.decodeBase32().toByteArray())
verifyResult.apply {
traceLocation.description shouldBe "My Birthday Party"
traceLocation.isBeforeStartTime(instant) shouldBe true
traceLocation.isAfterEndTime(instant) shouldBe false
}
}
}
fun verifyTraceLocationMapping2() {
val qrCodePayload = TraceLocationOuterClass.QRCodePayload.parseFrom(
BASE64_PAYLOAD_2.decodeBase64()!!.toByteArray()
)

@Test
fun verifyEventEndTimeWarning() = runBlockingTest {
val instant = Instant.now()
shouldNotThrowAny {
val verifyResult = traceLocationQRCodeVerifier.verify(ENCODED_EVENT1.decodeBase32().toByteArray())
verifyResult.apply {
traceLocation.description shouldBe "My Birthday Party"
traceLocation.isBeforeStartTime(instant) shouldBe false
traceLocation.isAfterEndTime(instant) shouldBe true
}
VerifiedTraceLocation(qrCodePayload).traceLocation
.apply {
locationId.base64() shouldBe "GMuCjqNmOdYyrFhyvFNTVEeLaZh+uShgUoY0LYJo4YQ="
qrCodePayload() shouldBe qrCodePayload
}
}
}

@Test
fun verifyEventWithInvalidKey() = runBlockingTest {
every { environmentSetup.appConfigVerificationKey } returns INVALID_PUB_KEY
shouldThrow<InvalidQRCodeSignatureException> {
traceLocationQRCodeVerifier.verify(ENCODED_EVENT1.decodeBase32().toByteArray())
}
}

@Test
fun eventHasMalformedData() = runBlockingTest {
shouldThrow<InvalidQRCodeDataException> {
traceLocationQRCodeVerifier.verify(
INVALID_ENCODED_EVENT.decodeBase32().toByteArray()
@Test
fun parcelization() {
val qrCodePayload = TraceLocationOuterClass.QRCodePayload.parseFrom(
BASE64_PAYLOAD_2.decodeBase64()!!.toByteArray()
)
}
}

@Test
fun decodingTest1() = runBlockingTest {
val signedTraceLocation = TraceLocationOuterClass.SignedTraceLocation.parseFrom(
ENCODED_EVENT1.decodeBase32().toByteArray()
)
val expectedSignature =
"MEQCIGVKfqPF2851IrEyDeVMazlRnIzLX16H6r1TB37PRzjbAiBGP13ADQcbQZsztKUCZMRcvnv5Mgdo0LY/v3qFMnrUkQ=="
val expectedVerifiedLocation = VerifiedTraceLocation(qrCodePayload)

val base32 = signedTraceLocation.toByteArray().toByteString().base32()

shouldNotThrowAny {
val verifyResult = traceLocationQRCodeVerifier.verify(base32.decodeBase32().toByteArray())

verifyResult.apply {
traceLocation.description shouldBe "My Birthday Party"
signedTraceLocation.signature.toByteArray().toByteString().base64() shouldBe expectedSignature
val bundle = Bundle().apply {
putParcelable("verifiedTraceLocation", expectedVerifiedLocation)
}
}
}

@Test
fun decodingTest2() = runBlockingTest {
val signedTraceLocation = TraceLocationOuterClass.SignedTraceLocation.parseFrom(
ENCODED_EVENT2.decodeBase32().toByteArray()
)
val expectedSignature =
"MEQCIDWRTM4ujn1GFPuHlgpUnQIWwwzwI8abxSrF5Er2I5HaAiAbucxg+6d3nC/Iwzo7AXehJAS20TRX1S2rl0LO8kcYxA=="
val parcelRaw = Parcel.obtain().apply {
writeBundle(bundle)
}.marshall()

val base32 = signedTraceLocation.toByteArray().toByteString().base32()

shouldNotThrowAny {
val verifyResult = traceLocationQRCodeVerifier.verify(base32.decodeBase32().toByteArray())

verifyResult.apply {
traceLocation.description shouldBe "Icecream Shop"
signedTraceLocation.signature.toByteArray().toByteString().base64() shouldBe expectedSignature
val restoredParcel = Parcel.obtain().apply {
unmarshall(parcelRaw, 0, parcelRaw.size)
setDataPosition(0)
}
}
}

@Test
fun testVerifiedTraceLocationMapping() {
shouldNotThrowAny {
val signedTraceLocation = TraceLocationOuterClass.SignedTraceLocation.parseFrom(
ENCODED_EVENT1.decodeBase32().toByteArray()
)

val traceLocation = TraceLocationOuterClass.TraceLocation.parseFrom(
ENCODED_EVENT1_LOCATION.decodeBase32().toByteArray()
)
val verifiedTraceLocation = VerifiedTraceLocation(
protoSignedTraceLocation = signedTraceLocation,
protoTraceLocation = traceLocation
).traceLocation

verifiedTraceLocation shouldBe TraceLocation(
guid = "3055331c-2306-43f3-9742-6d8fab54e848",
version = 1,
type = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_OTHER,
description = "My Birthday Party",
address = "at my place",
startDate = Instant.ofEpochSecond(2687955),
endDate = Instant.ofEpochSecond(2687991),
defaultCheckInLengthInMinutes = 0,
byteRepresentation = signedTraceLocation.location.toByteArray().toByteString(),
signature = signedTraceLocation.signature.toByteArray().toByteString()
)
val restoredData = restoredParcel.readBundle()!!.run {
classLoader = VerifiedTraceLocation::class.java.classLoader
getParcelable<VerifiedTraceLocation>("verifiedTraceLocation")
}
restoredData shouldBe expectedVerifiedLocation
}
}

*/

companion object {
private const val BASE64_PAYLOAD_1 = "CAESLAgBEhFNeSBCaXJ0aGRheSBQYXJ0eRoLYXQgbXkgcGxhY2Uo04ekAT" +
"D3h6QBGmUIARJbMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEst" +
"cUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3" +
"cAfQmxeuFMZAIX2+6A5XhoEMTIzNCIECAEQAg=="

// "signedLocation": {
// "location": {
// "guid": "3055331c-2306-43f3-9742-6d8fab54e848",
// "version": 1,
// "type": 2,
// "description": "My Birthday Party",
// "address": "at my place",
// "startTimestamp": 2687955,
// "endTimestamp": 2687991,
// "defaultCheckInLengthInMinutes": 0
// },
// "signature": "MEQCIGVKfqPF2851IrEyDeVMazlRnIzLX16H6r1TB37PRzjbAiBGP13ADQcbQZsztKUCZMRcvnv5Mgdo0LY/v3qFMnrUkQ=="
private const val ENCODED_EVENT1 =
"BJLAUJBTGA2TKMZTGFRS2MRTGA3C2NBTMYZS2OJXGQZC2NTEHBTGCYRVGRSTQNBYCAARQARCCFGXSICCNFZHI2DEMF4SAUDBOJ2HSKQLMF2CA3LZEBYGYYLDMUYNHB5EAE4PPB5EAFAAAESGGBCAEIDFJJ7KHRO3ZZ2SFMJSBXSUY2ZZKGOIZS27L2D6VPKTA57M6RZY3MBCARR7LXAA2BY3IGNTHNFFAJSMIXF6PP4TEB3I2C3D7P32QUZHVVER"
private const val ENCODED_EVENT1_LOCATION =
"BISDGMBVGUZTGMLDFUZDGMBWFU2DGZRTFU4TONBSFU3GIODGMFRDKNDFHA2DQEABDABCEEKNPEQEE2LSORUGIYLZEBIGC4TUPEVAWYLUEBWXSIDQNRQWGZJQ2OD2IAJY66D2IAKAAA"

// "signedLocation": {
// "location": {
// "guid": "fca84b37-61c0-4a7c-b2f8-825cadd506cf",
// "version": 1,
// "type": 1,
// "description": "Icecream Shop",
// "address": "Main Street 1",
// "startTimestamp": 0,
// "endTimestamp": 0,
// "defaultCheckInLengthInMinutes": 10
// },
// "signature": "MEQCIDWRTM4ujn1GFPuHlgpUnQIWwwzwI8abxSrF5Er2I5HaAiAbucxg+6d3nC/Iwzo7AXehJAS20TRX1S2rl0LO8kcYxA=="
private const val ENCODED_EVENT2 =
"BJHAUJDGMNQTQNDCGM3S2NRRMMYC2NDBG5RS2YRSMY4C2OBSGVRWCZDEGUYDMY3GCAARQAJCBVEWGZLDOJSWC3JAKNUG64BKBVGWC2LOEBJXI4TFMV2CAMJQAA4AAQAKCJDDARACEA2ZCTGOF2HH2RQU7ODZMCSUTUBBNQYM6AR4NG6FFLC6ISXWEOI5UARADO44YYH3U53ZYL6IYM5DWALXUESAJNWRGRL5KLNLS5BM54SHDDCA"

private const val INVALID_ENCODED_EVENT =
"NB2HI4DTHIXS653XO4XHK4TCMFXGI2LDORUW63TBOJ4S4Y3PNUXWIZLGNFXGKLTQNBYD65DFOJWT2VDIMUSTEMCDN53GSZBFGIYDCOI="

private const val PUB_KEY =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEafIKZOiRPuJWjKOUmKv7OTJWTyii4oCQLcGn3FgYoLQaJIvAM3Pl7anFDPPY/jxfqqrLyGc0f6hWQ9JPR3QjBw=="
private const val INVALID_PUB_KEY =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg=="
private const val BASE64_PAYLOAD_2 = "CAESIAgBEg1JY2VjcmVhbSBTaG9wGg1NYWluIFN0cmVldCAxGmUIARJ" +
"bMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT" +
"0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5XhoEMTIzNCIGCAEQARgK"
}
}
Loading