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

Add Crowd Notifier Public Key for different environments (EXPOSUREAPP-6118) #2729

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.core.content.edit
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import de.rki.coronawarnapp.environment.EnvironmentSetup.EnvKey.CROWD_NOTIFIER_PUBLIC_KEY
import de.rki.coronawarnapp.environment.EnvironmentSetup.EnvKey.DATA_DONATION
import de.rki.coronawarnapp.environment.EnvironmentSetup.EnvKey.DOWNLOAD
import de.rki.coronawarnapp.environment.EnvironmentSetup.EnvKey.LOG_UPLOAD
Expand Down Expand Up @@ -36,7 +37,8 @@ class EnvironmentSetup @Inject constructor(
DATA_DONATION("DATA_DONATION_CDN_URL"),
QRCODE_POSTER_TEMPLATE("QRCODE_POSTER_TEMPLATE_URL"),
LOG_UPLOAD("LOG_UPLOAD_SERVER_URL"),
SAFETYNET_API_KEY("SAFETYNET_API_KEY")
SAFETYNET_API_KEY("SAFETYNET_API_KEY"),
CROWD_NOTIFIER_PUBLIC_KEY("CROWD_NOTIFIER_PUBLIC_KEY")
}

enum class Type(val rawKey: String) {
Expand Down Expand Up @@ -128,6 +130,9 @@ class EnvironmentSetup @Inject constructor(
val safetyNetApiKey: String
get() = getEnvironmentValue(SAFETYNET_API_KEY).asString

val crowdNotifierPublicKey: String
get() = getEnvironmentValue(CROWD_NOTIFIER_PUBLIC_KEY).asString

val logUploadServerUrl: String
get() = getEnvironmentValue(LOG_UPLOAD).asString

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package de.rki.coronawarnapp.eventregistration.checkins.qrcode

import android.os.Parcelable
import de.rki.coronawarnapp.eventregistration.events.TraceLocationUserInput
import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity
import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
import kotlinx.parcelize.Parcelize
import okio.ByteString
import okio.ByteString.Companion.decodeBase64
import okio.ByteString.Companion.toByteString
import org.joda.time.Instant
import java.security.SecureRandom

const val TRACE_LOCATION_VERSION = 1

Expand All @@ -23,7 +20,7 @@ data class TraceLocation(
val endDate: Instant?,
val defaultCheckInLengthInMinutes: Int?,
val cryptographicSeed: ByteString,
val cnPublicKey: String = "hardcoded public key TODO: replace with real one",
val cnPublicKey: String,
val version: Int = TRACE_LOCATION_VERSION,
) : Parcelable {

Expand All @@ -46,14 +43,3 @@ fun TraceLocationEntity.toTraceLocation() = TraceLocation(
cnPublicKey = cnPublicKey,
version = version
)

fun TraceLocationUserInput.toTraceLocation(secureRandom: SecureRandom) = TraceLocation(
type = type,
description = description,
address = address,
startDate = startDate,
endDate = endDate,
defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes,
// cryptographic seed is a sequence of 16 random bytes
cryptographicSeed = ByteArray(16).apply { secureRandom.nextBytes(this) }.toByteString()
)
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
package de.rki.coronawarnapp.eventregistration.events

import de.rki.coronawarnapp.environment.EnvironmentSetup
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.toTraceLocation
import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
import okio.ByteString
import okio.ByteString.Companion.toByteString
import java.security.SecureRandom
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class TraceLocationCreator @Inject constructor(
private val repository: TraceLocationRepository,
private val secureRandom: SecureRandom
private val secureRandom: SecureRandom,
private val environmentSetup: EnvironmentSetup
) {

suspend fun createTraceLocation(traceLocationUserInput: TraceLocationUserInput): TraceLocation {
return repository.addTraceLocation(traceLocationUserInput.toTraceLocation(secureRandom))
val cnPublicKey = environmentSetup.crowdNotifierPublicKey

// cryptographic seed is a sequence of 16 random bytes
val cryptographicSeed = ByteArray(16).apply { secureRandom.nextBytes(this) }.toByteString()

val traceLocation = traceLocationUserInput.toTraceLocation(cryptographicSeed, cnPublicKey)
return repository.addTraceLocation(traceLocation)
}
}

fun TraceLocationUserInput.toTraceLocation(cryptographicSeed: ByteString, cnPublicKey: String) = TraceLocation(
type = type,
description = description,
address = address,
startDate = startDate,
endDate = endDate,
defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes,
cryptographicSeed = cryptographicSeed,
cnPublicKey = cnPublicKey
)
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class EnvironmentSetupTest : BaseTest() {
dataDonationCdnUrl shouldBe "https://datadonation-${env.rawKey}"
logUploadServerUrl shouldBe "https://logupload-${env.rawKey}"
qrCodePosterTemplateCdnUrl shouldBe "https://qrcodepostertemplate-${env.rawKey}"
crowdNotifierPublicKey shouldBe "123_abc-${env.rawKey}"
}
}
}
Expand Down Expand Up @@ -126,7 +127,8 @@ class EnvironmentSetupTest : BaseTest() {
EnvironmentSetup.EnvKey.LOG_UPLOAD.rawKey shouldBe "LOG_UPLOAD_SERVER_URL"
EnvironmentSetup.EnvKey.SAFETYNET_API_KEY.rawKey shouldBe "SAFETYNET_API_KEY"
EnvironmentSetup.EnvKey.QRCODE_POSTER_TEMPLATE.rawKey shouldBe "QRCODE_POSTER_TEMPLATE_URL"
EnvironmentSetup.EnvKey.values().size shouldBe 9
EnvironmentSetup.EnvKey.CROWD_NOTIFIER_PUBLIC_KEY.rawKey shouldBe "CROWD_NOTIFIER_PUBLIC_KEY"
EnvironmentSetup.EnvKey.values().size shouldBe 10
}

companion object {
Expand All @@ -149,7 +151,8 @@ class EnvironmentSetupTest : BaseTest() {
"LOG_UPLOAD_SERVER_URL": "https://logupload-PROD",
"QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-PROD",
"SAFETYNET_API_KEY": "placeholder-PROD",
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-PROD"
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-PROD",
"CROWD_NOTIFIER_PUBLIC_KEY": "123_abc-PROD"
},
"DEV": {
"USE_EUR_KEY_PKGS" : false,
Expand All @@ -160,7 +163,8 @@ class EnvironmentSetupTest : BaseTest() {
"LOG_UPLOAD_SERVER_URL": "https://logupload-DEV",
"QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-DEV",
"SAFETYNET_API_KEY": "placeholder-DEV",
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-DEV"
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-DEV",
"CROWD_NOTIFIER_PUBLIC_KEY": "123_abc-DEV"
},
"INT": {
"USE_EUR_KEY_PKGS" : false,
Expand All @@ -171,7 +175,8 @@ class EnvironmentSetupTest : BaseTest() {
"LOG_UPLOAD_SERVER_URL": "https://logupload-INT",
"QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-INT",
"SAFETYNET_API_KEY": "placeholder-INT",
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-INT"
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-INT",
"CROWD_NOTIFIER_PUBLIC_KEY": "123_abc-INT"
},
"WRU": {
"USE_EUR_KEY_PKGS" : false,
Expand All @@ -183,7 +188,8 @@ class EnvironmentSetupTest : BaseTest() {
"QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-WRU",
"SAFETYNET_API_KEY": "placeholder-WRU",
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU",
"CREATE_TRACELOCATION_URL": "https://tracelocation-WRU"
"CREATE_TRACELOCATION_URL": "https://tracelocation-WRU",
"CROWD_NOTIFIER_PUBLIC_KEY": "123_abc-WRU"
},
"WRU-XD": {
"USE_EUR_KEY_PKGS" : true,
Expand All @@ -194,7 +200,8 @@ class EnvironmentSetupTest : BaseTest() {
"LOG_UPLOAD_SERVER_URL": "https://logupload-WRU-XD",
"QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-WRU-XD",
"SAFETYNET_API_KEY": "placeholder-WRU-XD",
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU-XD"
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU-XD",
"CROWD_NOTIFIER_PUBLIC_KEY": "123_abc-WRU-XD"
},
"WRU-XA": {
"USE_EUR_KEY_PKGS" : true,
Expand All @@ -205,7 +212,8 @@ class EnvironmentSetupTest : BaseTest() {
"LOG_UPLOAD_SERVER_URL": "https://logupload-WRU-XA",
"QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-WRU-XA",
"SAFETYNET_API_KEY": "placeholder-WRU-XA",
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU-XA"
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU-XA",
"CROWD_NOTIFIER_PUBLIC_KEY": "123_abc-WRU-XA"
},
"LOCAL": {
"USE_EUR_KEY_PKGS" : true,
Expand All @@ -216,7 +224,8 @@ class EnvironmentSetupTest : BaseTest() {
"LOG_UPLOAD_SERVER_URL": "https://logupload-LOCAL",
"QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-LOCAL",
"SAFETYNET_API_KEY": "placeholder-LOCAL",
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-LOCAL"
"PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-LOCAL",
"CROWD_NOTIFIER_PUBLIC_KEY": "123_abc-LOCAL"
}
}
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,41 @@
package de.rki.coronawarnapp.eventregistration.checkins.qrcode

import de.rki.coronawarnapp.eventregistration.events.TraceLocationUserInput
import de.rki.coronawarnapp.eventregistration.events.toTraceLocation
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.decodeHex
import okio.ByteString.Companion.toByteString
import okio.ByteString.Companion.encode
import org.joda.time.Instant
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.security.SecureRandom
import kotlin.random.Random

internal class TraceLocationUserInputToTraceLocationMapperTest {

@MockK private lateinit var secureRandom: SecureRandom

@BeforeEach
fun setUp() {
MockKAnnotations.init(this)
}

@Test
fun toTraceLocation() {

every { secureRandom.nextBytes(any()) } answers {
val byteArray = arg<ByteArray>(0)
Random(0).nextBytes(byteArray)
}

TraceLocationUserInput(
type = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_PRIVATE_EVENT,
description = "Top Secret Private Event",
address = "top secret address",
startDate = Instant.parse("2020-01-01T14:00:00.000Z"),
endDate = Instant.parse("2020-01-01T18:00:00.000Z"),
defaultCheckInLengthInMinutes = 180
).toTraceLocation(secureRandom) shouldBe TraceLocation(
).toTraceLocation("cryptographicSeed".encode(), "cnPublicKey123") shouldBe TraceLocation(
id = 0,
type = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_PRIVATE_EVENT,
description = "Top Secret Private Event",
address = "top secret address",
startDate = Instant.parse("2020-01-01T14:00:00.000Z"),
endDate = Instant.parse("2020-01-01T18:00:00.000Z"),
defaultCheckInLengthInMinutes = 180,
cryptographicSeed = "2cc2b48c50aefe53b3974ed91e6b4ea9".decodeHex().toByteArray().toByteString(),
cnPublicKey = "hardcoded public key TODO: replace with real one"
cryptographicSeed = "cryptographicSeed".encode(),
cnPublicKey = "cnPublicKey123"
)
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
package de.rki.coronawarnapp.eventregistration.events

import de.rki.coronawarnapp.eventregistration.checkins.qrcode.toTraceLocation
import de.rki.coronawarnapp.environment.EnvironmentSetup
import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import kotlinx.coroutines.test.runBlockingTest
import okio.ByteString.Companion.decodeHex
import okio.ByteString.Companion.toByteString
import org.joda.time.Instant
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import java.security.SecureRandom
import kotlin.random.Random

internal class TraceLocationCreatorTest : BaseTest() {

@MockK lateinit var repository: TraceLocationRepository
@RelaxedMockK lateinit var secureRandom: SecureRandom
@MockK private lateinit var environmentSetup: EnvironmentSetup

@BeforeEach
fun setUp() {
MockKAnnotations.init(this)
every { environmentSetup.crowdNotifierPublicKey } returns "cnPublicKey123"

every { secureRandom.nextBytes(any()) } answers {
val byteArray = arg<ByteArray>(0)
Random(0).nextBytes(byteArray)
}
}

private fun createInstance() = TraceLocationCreator(repository, secureRandom)
private fun createInstance() = TraceLocationCreator(repository, secureRandom, environmentSetup)

@Test
fun `createTraceLocation() should return traceLocation and store it in repository when everything works fine`() =
Expand All @@ -41,7 +52,10 @@ internal class TraceLocationCreatorTest : BaseTest() {
defaultCheckInLengthInMinutes = 180
)

val expectedTraceLocation = userInput.toTraceLocation(secureRandom)
val expectedTraceLocation = userInput.toTraceLocation(
cryptographicSeed = "2cc2b48c50aefe53b3974ed91e6b4ea9".decodeHex().toByteArray().toByteString(),
cnPublicKey = "cnPublicKey123"
)

coEvery { repository.addTraceLocation(any()) } returns expectedTraceLocation

Expand Down