diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt index b4295fc63485450d62cca4ca0ddcdd2552c533fe..1445d927e4f2eed79b3ba219c0708249eb33ee07 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt @@ -5,8 +5,8 @@ import dagger.Module import dagger.multibindings.IntoSet import de.rki.coronawarnapp.coronatest.server.VerificationModule import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor -import de.rki.coronawarnapp.coronatest.type.pcr.PCRProcessor -import de.rki.coronawarnapp.coronatest.type.rapidantigen.RAProcessor +import de.rki.coronawarnapp.coronatest.type.pcr.PCRTestProcessor +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RATestProcessor @Module( includes = [VerificationModule::class] @@ -16,12 +16,12 @@ abstract class CoronaTestModule { @Binds @IntoSet abstract fun pcrProcessor( - processor: PCRProcessor + processor: PCRTestProcessor ): CoronaTestProcessor @Binds @IntoSet abstract fun ratProcessor( - processor: RAProcessor + processor: RATestProcessor ): CoronaTestProcessor } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestCertificateRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestCertificateRepository.kt index 6bd544343d2ae905da542d56284f30a4ec755340..b50985a1d33607cb1ee978e3a63c8ce8e530d8d7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestCertificateRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestCertificateRepository.kt @@ -1,44 +1,30 @@ package de.rki.coronawarnapp.coronatest -import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.coronatest.storage.TestCertificateStorage import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.TestCertificateWrapper import de.rki.coronawarnapp.coronatest.type.common.TestCertificateContainer import de.rki.coronawarnapp.coronatest.type.common.TestCertificateIdentifier -import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateContainer -import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACertificateContainer -import de.rki.coronawarnapp.covidcertificate.exception.InvalidHealthCertificateException.ErrorCode.RSA_DECRYPTION_FAILED -import de.rki.coronawarnapp.covidcertificate.exception.InvalidHealthCertificateException.ErrorCode.RSA_KP_GENERATION_FAILED -import de.rki.coronawarnapp.covidcertificate.exception.InvalidTestCertificateException -import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException -import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_202 -import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateServer -import de.rki.coronawarnapp.covidcertificate.server.TestCertificateComponents +import de.rki.coronawarnapp.coronatest.type.common.TestCertificateProcessor +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateData +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACertificateData import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCodeExtractor -import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.coroutine.DispatcherProvider -import de.rki.coronawarnapp.util.encryption.rsa.RSACryptography -import de.rki.coronawarnapp.util.encryption.rsa.RSAKeyPairGenerator import de.rki.coronawarnapp.util.flow.HotDataFlow import de.rki.coronawarnapp.util.flow.combine import de.rki.coronawarnapp.util.mutate import de.rki.coronawarnapp.vaccination.core.repository.ValueSetsRepository import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.plus import kotlinx.coroutines.withContext -import okio.ByteString.Companion.decodeBase64 -import org.joda.time.Duration import timber.log.Timber import java.util.UUID import javax.inject.Inject @@ -49,14 +35,10 @@ import javax.inject.Singleton class TestCertificateRepository @Inject constructor( @AppScope private val appScope: CoroutineScope, private val dispatcherProvider: DispatcherProvider, - private val timeStamper: TimeStamper, private val storage: TestCertificateStorage, - private val certificateServer: CovidCertificateServer, - private val rsaKeyPairGenerator: RSAKeyPairGenerator, - private val rsaCryptography: RSACryptography, private val qrCodeExtractor: TestCertificateQRCodeExtractor, - private val appConfigProvider: AppConfigProvider, - private val valueSetsRepository: ValueSetsRepository, + private val processor: TestCertificateProcessor, + valueSetsRepository: ValueSetsRepository, ) { private val internalData: HotDataFlow<Map<TestCertificateIdentifier, TestCertificateContainer>> = HotDataFlow( @@ -64,9 +46,17 @@ class TestCertificateRepository @Inject constructor( scope = appScope + dispatcherProvider.Default, sharingBehavior = SharingStarted.Eagerly, ) { - storage.testCertificates.map { it.identifier to it }.toMap().also { - Timber.tag(TAG).v("Restored TestCertificate data: %s", it) - } + storage.testCertificates + .map { + TestCertificateContainer( + data = it, + qrCodeExtractor = qrCodeExtractor + ) + } + .map { it.identifier to it } + .toMap().also { + Timber.tag(TAG).v("Restored TestCertificate data: %s", it) + } } val certificates: Flow<Set<TestCertificateWrapper>> = combine( @@ -84,9 +74,10 @@ class TestCertificateRepository @Inject constructor( init { internalData.data .onStart { Timber.tag(TAG).d("Observing TestCertificateContainer data.") } - .onEach { - Timber.tag(TAG).v("TestCertificateContainer data changed: %s", it) - storage.testCertificates = it.values.toSet() + .onEach { entrySets -> + val values = entrySets.values + Timber.tag(TAG).v("TestCertificateContainer data changed: %s", values) + storage.testCertificates = values.map { it.data }.toSet() } .catch { it.reportProblem(TAG, "Failed to snapshot TestCertificateContainer data to storage.") @@ -119,22 +110,24 @@ class TestCertificateRepository @Inject constructor( val identifier = UUID.randomUUID().toString() - val certificate = when (test.type) { - CoronaTest.Type.PCR -> PCRCertificateContainer( + val data = when (test.type) { + CoronaTest.Type.PCR -> PCRCertificateData( identifier = identifier, registeredAt = test.registeredAt, registrationToken = test.registrationToken, ) - CoronaTest.Type.RAPID_ANTIGEN -> RACertificateContainer( + CoronaTest.Type.RAPID_ANTIGEN -> RACertificateData( identifier = identifier, registeredAt = test.registeredAt, registrationToken = test.registrationToken, ) - }.also { - it.qrCodeExtractor = qrCodeExtractor } - Timber.tag(TAG).d("Adding test certificate entry: %s", certificate) - mutate { this[certificate.identifier] = certificate } + val container = TestCertificateContainer( + data = data, + qrCodeExtractor = qrCodeExtractor, + ) + Timber.tag(TAG).d("Adding test certificate entry: %s", container) + mutate { this[container.identifier] = container } } return newData.values.single { it.registrationToken == test.registrationToken } @@ -175,10 +168,7 @@ class TestCertificateRepository @Inject constructor( mutate { toRefresh.forEach { workedOnIds.add(it.identifier) - this[it.identifier] = when (it.type) { - CoronaTest.Type.PCR -> (it as PCRCertificateContainer).copy(isUpdatingData = true) - CoronaTest.Type.RAPID_ANTIGEN -> (it as RACertificateContainer).copy(isUpdatingData = true) - } + this[it.identifier] = it.copy(isUpdatingData = false) } } } @@ -192,7 +182,8 @@ class TestCertificateRepository @Inject constructor( .map { cert -> withContext(dispatcherProvider.IO) { try { - RefreshResult(registerPublicKey(cert)) + val updatedData = processor.registerPublicKey(cert.data) + RefreshResult(cert.copy(data = updatedData)) } catch (e: Exception) { Timber.tag(TAG).e(e, "Failed to register public key for %s", cert) RefreshResult(cert, e) @@ -221,7 +212,8 @@ class TestCertificateRepository @Inject constructor( .map { cert -> withContext(dispatcherProvider.IO) { try { - RefreshResult(obtainCertificate(cert)) + val updatedData = processor.obtainCertificate(cert.data) + RefreshResult(cert.copy(data = updatedData)) } catch (e: Exception) { Timber.tag(TAG).e(e, "Failed to retrieve certificate components for %s", cert) RefreshResult(cert, e) @@ -246,10 +238,7 @@ class TestCertificateRepository @Inject constructor( mutate { certs.forEach { - this[it.identifier] = when (it.type) { - CoronaTest.Type.PCR -> (it as PCRCertificateContainer).copy(isUpdatingData = false) - CoronaTest.Type.RAPID_ANTIGEN -> (it as RACertificateContainer).copy(isUpdatingData = false) - } + this[it.identifier] = it.copy(isUpdatingData = false) } } } @@ -257,125 +246,6 @@ class TestCertificateRepository @Inject constructor( return refreshCallResults.values.toSet() } - /** - * Register the public key with the server, a shortwhile later, - * the test certificate components should be available, via [obtainCertificate]. - */ - private suspend fun registerPublicKey( - cert: TestCertificateContainer - ): TestCertificateContainer { - Timber.tag(TAG).d("registerPublicKey(cert=%s)", cert) - - if (cert.isPublicKeyRegistered) { - Timber.tag(TAG).d("Public key is already registered for %s", cert) - return cert - } - - val rsaKeyPair = try { - rsaKeyPairGenerator.generate() - } catch (e: Throwable) { - throw InvalidTestCertificateException(RSA_KP_GENERATION_FAILED) - } - - certificateServer.registerPublicKeyForTest( - testRegistrationToken = cert.registrationToken, - publicKey = rsaKeyPair.publicKey, - ) - Timber.tag(TAG).i("Public key successfully registered for %s", cert) - - val nowUTC = timeStamper.nowUTC - - return when (cert.type) { - CoronaTest.Type.PCR -> (cert as PCRCertificateContainer).copy( - publicKeyRegisteredAt = nowUTC, - rsaPublicKey = rsaKeyPair.publicKey, - rsaPrivateKey = rsaKeyPair.privateKey, - ) - CoronaTest.Type.RAPID_ANTIGEN -> (cert as RACertificateContainer).copy( - publicKeyRegisteredAt = nowUTC, - rsaPublicKey = rsaKeyPair.publicKey, - rsaPrivateKey = rsaKeyPair.privateKey, - ) - } - } - - /** - * Try to obtain the actual certificate. - * PublicKey registration and certificate retrieval are two steps, because if we manage to register our public key, - * but fail to get the certificate, we are still one step further. - * - * The server does not immediately return the test certificate components after registering the public key. - */ - private suspend fun obtainCertificate( - cert: TestCertificateContainer - ): TestCertificateContainer { - Timber.tag(TAG).d("requestCertificate(cert=%s)", cert) - - if (!cert.isPublicKeyRegistered) throw IllegalStateException("Public key is not registered yet.") - - if (!cert.isCertificateRetrievalPending) { - Timber.tag(TAG).d("Dcc has already been retrieved for %s", cert) - return cert - } - - val certConfig = appConfigProvider.currentConfig.first().covidCertificateParameters.testCertificate - - val nowUTC = timeStamper.nowUTC - val certAvailableAt = cert.publicKeyRegisteredAt!!.plus(certConfig.waitAfterPublicKeyRegistration) - val certAvailableIn = Duration(nowUTC, certAvailableAt) - - if (certAvailableIn > Duration.ZERO && certAvailableIn <= certConfig.waitAfterPublicKeyRegistration) { - Timber.tag(TAG).d("Delaying certificate retrieval by %d ms", certAvailableIn.millis) - delay(certAvailableIn.millis) - } - - val executeRequest: suspend () -> TestCertificateComponents = { - certificateServer.requestCertificateForTest(testRegistrationToken = cert.registrationToken) - } - - val components = try { - executeRequest() - } catch (e: TestCertificateServerException) { - if (e.errorCode == DCC_COMP_202) { - delay(certConfig.waitForRetry.millis) - executeRequest() - } else { - throw e - } - } - Timber.tag(TAG).i("Test certificate components successfully request for %s: %s", cert, components) - - val encryptionKey = try { - rsaCryptography.decrypt( - toDecrypt = components.dataEncryptionKeyBase64.decodeBase64()!!, - privateKey = cert.rsaPrivateKey!! - ) - } catch (e: Throwable) { - Timber.tag(TAG).e(e, "RSA_DECRYPTION_FAILED") - throw InvalidTestCertificateException(RSA_DECRYPTION_FAILED) - } - - val extractedData = qrCodeExtractor.extract( - decryptionKey = encryptionKey.toByteArray(), - rawCoseObjectEncrypted = components.encryptedCoseTestCertificateBase64.decodeBase64()!!.toByteArray() - ) - - val nowUtc = timeStamper.nowUTC - - return when (cert.type) { - CoronaTest.Type.PCR -> (cert as PCRCertificateContainer).copy( - testCertificateQrCode = extractedData.qrCode, - certificateReceivedAt = nowUtc, - ) - CoronaTest.Type.RAPID_ANTIGEN -> (cert as RACertificateContainer).copy( - testCertificateQrCode = extractedData.qrCode, - certificateReceivedAt = nowUtc, - ) - }.also { - it.preParsedData = extractedData.testCertificateData - } - } - /** * [deleteCertificate] does not throw an exception, if the deletion target already does not exist. */ diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorage.kt index e6933d9507986a097e0554706f737dbe547aff5a..c56688b65a75b9250475acd91bd5be14c64ed68e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorage.kt @@ -6,9 +6,9 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.coronatest.type.common.TestCertificateContainer -import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateContainer -import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACertificateContainer +import de.rki.coronawarnapp.coronatest.type.common.StoredTestCertificateData +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateData +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACertificateData import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.serialization.BaseGson import de.rki.coronawarnapp.vaccination.core.repository.storage.ContainerPostProcessor @@ -35,29 +35,29 @@ class TestCertificateStorage @Inject constructor( } private val typeTokenPCR by lazy { - object : TypeToken<Set<PCRCertificateContainer>>() {}.type + object : TypeToken<Set<PCRCertificateData>>() {}.type } private val typeTokenRA by lazy { - object : TypeToken<Set<RACertificateContainer>>() {}.type + object : TypeToken<Set<RACertificateData>>() {}.type } - var testCertificates: Collection<TestCertificateContainer> + var testCertificates: Collection<StoredTestCertificateData> get() { Timber.tag(TAG).d("load()") - val pcrCertContainers: Set<PCRCertificateContainer> = run { + val pcrCertContainers: Set<PCRCertificateData> = run { val raw = prefs.getString(PKEY_DATA_PCR, null) ?: return@run emptySet() - gson.fromJson<Set<PCRCertificateContainer>>(raw, typeTokenPCR).onEach { + gson.fromJson<Set<PCRCertificateData>>(raw, typeTokenPCR).onEach { Timber.tag(TAG).v("PCR loaded: %s", it) requireNotNull(it.identifier) requireNotNull(it.type) { "PCR type should not be null, GSON footgun." } } } - val raCerts: Set<RACertificateContainer> = run { + val raCerts: Set<RACertificateData> = run { val raw = prefs.getString(PKEY_DATA_RA, null) ?: return@run emptySet() - gson.fromJson<Set<RACertificateContainer>>(raw, typeTokenRA).onEach { + gson.fromJson<Set<RACertificateData>>(raw, typeTokenRA).onEach { Timber.tag(TAG).v("RA loaded: %s", it) requireNotNull(it.identifier) requireNotNull(it.type) { "RA type should not be null, GSON footgun." } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/StoredTestCertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/StoredTestCertificateData.kt new file mode 100644 index 0000000000000000000000000000000000000000..70234ef8c25fd69b8cb7b23ff0abc2f2417a1460 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/StoredTestCertificateData.kt @@ -0,0 +1,21 @@ +package de.rki.coronawarnapp.coronatest.type.common + +import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.coronatest.type.RegistrationToken +import de.rki.coronawarnapp.util.encryption.rsa.RSAKey +import okio.ByteString +import org.joda.time.Instant + +interface StoredTestCertificateData { + val identifier: TestCertificateIdentifier + val registrationToken: RegistrationToken + val type: CoronaTest.Type + val registeredAt: Instant + val publicKeyRegisteredAt: Instant? + val rsaPublicKey: RSAKey.Public? + val rsaPrivateKey: RSAKey.Private? + val certificateReceivedAt: Instant? + val encryptedDataEncryptionkey: ByteString? + val encryptedDccCose: ByteString? + val testCertificateQrCode: String? +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateContainer.kt index f49ffb3d09f42e9af6f2823a07fcb007a2b2a0ce..13f2b62f62feb65d337d210081aa9b937b68fe63 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateContainer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateContainer.kt @@ -1,51 +1,32 @@ package de.rki.coronawarnapp.coronatest.type.common -import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.coronatest.type.RegistrationToken import de.rki.coronawarnapp.covidcertificate.test.TestCertificate import de.rki.coronawarnapp.covidcertificate.test.TestCertificateData import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCodeExtractor -import de.rki.coronawarnapp.util.encryption.rsa.RSAKey import de.rki.coronawarnapp.vaccination.core.CertificatePersonIdentifier import de.rki.coronawarnapp.vaccination.core.personIdentifier import de.rki.coronawarnapp.vaccination.core.qrcode.QrCodeString import de.rki.coronawarnapp.vaccination.core.server.valueset.valuesets.TestCertificateValueSets -import okio.ByteString import org.joda.time.Instant import org.joda.time.LocalDate import java.util.Locale -abstract class TestCertificateContainer { - abstract val identifier: TestCertificateIdentifier - abstract val registrationToken: RegistrationToken - abstract val type: CoronaTest.Type - abstract val registeredAt: Instant - abstract val publicKeyRegisteredAt: Instant? - abstract val rsaPublicKey: RSAKey.Public? - abstract val rsaPrivateKey: RSAKey.Private? - abstract val certificateReceivedAt: Instant? - abstract val encryptedDataEncryptionkey: ByteString? - abstract val encryptedDccCose: ByteString? - abstract val testCertificateQrCode: String? - - abstract val isUpdatingData: Boolean - - // Either set by [ContainerPostProcessor] (if from storage) or during first creation (when new) - @Transient internal lateinit var qrCodeExtractor: TestCertificateQRCodeExtractor - - // When we create this container initially, we don't need to pare the data again, we already have it. - @Transient internal var preParsedData: TestCertificateData? = null +data class TestCertificateContainer( + internal val data: StoredTestCertificateData, + private val qrCodeExtractor: TestCertificateQRCodeExtractor, + val isUpdatingData: Boolean = false, +) : StoredTestCertificateData by data { @delegate:Transient private val certificateData: TestCertificateData by lazy { - preParsedData ?: testCertificateQrCode!!.let { qrCodeExtractor.extract(it).testCertificateData } + data.testCertificateQrCode!!.let { qrCodeExtractor.extract(it).testCertificateData } } val isPublicKeyRegistered: Boolean - get() = publicKeyRegisteredAt != null + get() = data.publicKeyRegisteredAt != null val isCertificateRetrievalPending: Boolean - get() = certificateReceivedAt == null + get() = data.certificateReceivedAt == null val certificateId: String? get() { @@ -108,9 +89,7 @@ abstract class TestCertificateContainer { get() = header.expiresAt override val qrCode: QrCodeString - get() = testCertificateQrCode!! + get() = data.testCertificateQrCode!! } } } - -typealias TestCertificateIdentifier = String diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateIdentifier.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateIdentifier.kt new file mode 100644 index 0000000000000000000000000000000000000000..1c3642a39554921616e505a07100eadda8e83f82 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateIdentifier.kt @@ -0,0 +1,3 @@ +package de.rki.coronawarnapp.coronatest.type.common + +typealias TestCertificateIdentifier = String diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateProcessor.kt new file mode 100644 index 0000000000000000000000000000000000000000..2bf24175b2720966b49f07b0f62b3a5e95f9682c --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateProcessor.kt @@ -0,0 +1,158 @@ +package de.rki.coronawarnapp.coronatest.type.common + +import dagger.Reusable +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateData +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACertificateData +import de.rki.coronawarnapp.covidcertificate.exception.InvalidHealthCertificateException +import de.rki.coronawarnapp.covidcertificate.exception.InvalidTestCertificateException +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException +import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateServer +import de.rki.coronawarnapp.covidcertificate.server.TestCertificateComponents +import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCodeExtractor +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.encryption.rsa.RSACryptography +import de.rki.coronawarnapp.util.encryption.rsa.RSAKeyPairGenerator +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import okio.ByteString.Companion.decodeBase64 +import org.joda.time.Duration +import timber.log.Timber +import javax.inject.Inject + +@Reusable +class TestCertificateProcessor @Inject constructor( + private val timeStamper: TimeStamper, + private val certificateServer: CovidCertificateServer, + private val rsaKeyPairGenerator: RSAKeyPairGenerator, + private val rsaCryptography: RSACryptography, + private val appConfigProvider: AppConfigProvider, + private val qrCodeExtractor: TestCertificateQRCodeExtractor, +) { + + /** + * Register the public key with the server, a shortwhile later, + * the test certificate components should be available, via [obtainCertificate]. + */ + internal suspend fun registerPublicKey( + data: StoredTestCertificateData + ): StoredTestCertificateData { + Timber.tag(TAG).d("registerPublicKey(cert=%s)", data) + + if (data.publicKeyRegisteredAt != null) { + Timber.tag(TAG).d("Public key is already registered for %s", data) + return data + } + + val rsaKeyPair = try { + rsaKeyPairGenerator.generate() + } catch (e: Throwable) { + throw InvalidTestCertificateException(InvalidHealthCertificateException.ErrorCode.RSA_KP_GENERATION_FAILED) + } + + certificateServer.registerPublicKeyForTest( + testRegistrationToken = data.registrationToken, + publicKey = rsaKeyPair.publicKey, + ) + Timber.tag(TAG).i("Public key successfully registered for %s", data) + + val nowUTC = timeStamper.nowUTC + + return when (data.type) { + CoronaTest.Type.PCR -> (data as PCRCertificateData).copy( + publicKeyRegisteredAt = nowUTC, + rsaPublicKey = rsaKeyPair.publicKey, + rsaPrivateKey = rsaKeyPair.privateKey, + ) + CoronaTest.Type.RAPID_ANTIGEN -> (data as RACertificateData).copy( + publicKeyRegisteredAt = nowUTC, + rsaPublicKey = rsaKeyPair.publicKey, + rsaPrivateKey = rsaKeyPair.privateKey, + ) + } + } + + /** + * Try to obtain the actual certificate. + * PublicKey registration and certificate retrieval are two steps, because if we manage to register our public key, + * but fail to get the certificate, we are still one step further. + * + * The server does not immediately return the test certificate components after registering the public key. + */ + internal suspend fun obtainCertificate( + data: StoredTestCertificateData + ): StoredTestCertificateData { + Timber.tag(TAG).d("requestCertificate(cert=%s)", data) + + if (data.publicKeyRegisteredAt == null) { + throw IllegalStateException("Public key is not registered yet.") + } + + if (data.certificateReceivedAt != null) { + Timber.tag(TAG).d("Dcc has already been retrieved for %s", data) + return data + } + + val certConfig = appConfigProvider.currentConfig.first().covidCertificateParameters.testCertificate + + val nowUTC = timeStamper.nowUTC + val certAvailableAt = data.publicKeyRegisteredAt!!.plus(certConfig.waitAfterPublicKeyRegistration) + val certAvailableIn = Duration(nowUTC, certAvailableAt) + + if (certAvailableIn > Duration.ZERO && certAvailableIn <= certConfig.waitAfterPublicKeyRegistration) { + Timber.tag(TAG) + .d("Delaying certificate retrieval by %d ms", certAvailableIn.millis) + delay(certAvailableIn.millis) + } + + val executeRequest: suspend () -> TestCertificateComponents = { + certificateServer.requestCertificateForTest(testRegistrationToken = data.registrationToken) + } + + val components = try { + executeRequest() + } catch (e: TestCertificateServerException) { + if (e.errorCode == TestCertificateServerException.ErrorCode.DCC_COMP_202) { + delay(certConfig.waitForRetry.millis) + executeRequest() + } else { + throw e + } + } + Timber.tag(TAG) + .i("Test certificate components successfully request for %s: %s", data, components) + + val encryptionKey = try { + rsaCryptography.decrypt( + toDecrypt = components.dataEncryptionKeyBase64.decodeBase64()!!, + privateKey = data.rsaPrivateKey!! + ) + } catch (e: Throwable) { + Timber.tag(TAG).e(e, "RSA_DECRYPTION_FAILED") + throw InvalidTestCertificateException(InvalidHealthCertificateException.ErrorCode.RSA_DECRYPTION_FAILED) + } + + val extractedData = qrCodeExtractor.extract( + decryptionKey = encryptionKey.toByteArray(), + rawCoseObjectEncrypted = components.encryptedCoseTestCertificateBase64.decodeBase64()!!.toByteArray() + ) + + val nowUtc = timeStamper.nowUTC + + return when (data.type) { + CoronaTest.Type.PCR -> (data as PCRCertificateData).copy( + testCertificateQrCode = extractedData.qrCode, + certificateReceivedAt = nowUtc, + ) + CoronaTest.Type.RAPID_ANTIGEN -> (data as RACertificateData).copy( + testCertificateQrCode = extractedData.qrCode, + certificateReceivedAt = nowUtc, + ) + } + } + + companion object { + private val TAG = TestCertificateProcessor::class.simpleName!! + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCertificateContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCertificateData.kt similarity index 87% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCertificateContainer.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCertificateData.kt index bee0ea96cbe785f848431428613e264ab35fe997..148d5c5a87a61d8cdec492cfc6b9bbdef096d2d7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCertificateContainer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCertificateData.kt @@ -3,12 +3,12 @@ package de.rki.coronawarnapp.coronatest.type.pcr import com.google.gson.annotations.SerializedName import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.RegistrationToken -import de.rki.coronawarnapp.coronatest.type.common.TestCertificateContainer +import de.rki.coronawarnapp.coronatest.type.common.StoredTestCertificateData import de.rki.coronawarnapp.util.encryption.rsa.RSAKey import okio.ByteString import org.joda.time.Instant -data class PCRCertificateContainer internal constructor( +data class PCRCertificateData internal constructor( @SerializedName("identifier") override val identifier: String, @@ -38,9 +38,7 @@ data class PCRCertificateContainer internal constructor( @SerializedName("testCertificateQrCode") override val testCertificateQrCode: String? = null, - - @Transient override val isUpdatingData: Boolean = false, -) : TestCertificateContainer() { +) : StoredTestCertificateData { // Otherwise GSON unsafes reflection to create this class, and sets the LAZY to null @Suppress("unused") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt similarity index 98% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt index b46f1f7ad3c5061673177472a5eb1e3f303fb1f6..58bfe8a28d16fd55ab3d07efd251fd7f4458e288 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt @@ -37,7 +37,7 @@ import timber.log.Timber import javax.inject.Inject @Reusable -class PCRProcessor @Inject constructor( +class PCRTestProcessor @Inject constructor( private val timeStamper: TimeStamper, private val submissionService: CoronaTestService, private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, @@ -277,7 +277,7 @@ private fun CoronaTestResult.toValidatedResult(): CoronaTestResult { return if (isValid) { this } else { - Timber.tag(PCRProcessor.TAG).e("Server returned invalid PCR testresult $this") + Timber.tag(PCRTestProcessor.TAG).e("Server returned invalid PCR testresult $this") PCR_INVALID } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACertificateContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACertificateData.kt similarity index 88% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACertificateContainer.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACertificateData.kt index ccea190914256452f13550f07db1a32bbbbf2667..89f8cfaf368a9916ca7630a210fc5aff2149035c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACertificateContainer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACertificateData.kt @@ -3,12 +3,12 @@ package de.rki.coronawarnapp.coronatest.type.rapidantigen import com.google.gson.annotations.SerializedName import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.RegistrationToken -import de.rki.coronawarnapp.coronatest.type.common.TestCertificateContainer +import de.rki.coronawarnapp.coronatest.type.common.StoredTestCertificateData import de.rki.coronawarnapp.util.encryption.rsa.RSAKey import okio.ByteString import org.joda.time.Instant -data class RACertificateContainer( +data class RACertificateData( @SerializedName("identifier") override val identifier: String, @@ -38,9 +38,7 @@ data class RACertificateContainer( @SerializedName("testCertificateQrCode") override val testCertificateQrCode: String? = null, - - @Transient override val isUpdatingData: Boolean = false, -) : TestCertificateContainer() { +) : StoredTestCertificateData { // Otherwise GSON unsafes reflection to create this class, and sets the LAZY to null @Suppress("unused") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt similarity index 95% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt index 9f5364756fe00ed53dd92d27956e23bbc798a01f..075881497a8447e524264062768309f02867f60d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt @@ -21,7 +21,6 @@ import de.rki.coronawarnapp.coronatest.server.VerificationServer import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor import de.rki.coronawarnapp.coronatest.type.CoronaTestService -import de.rki.coronawarnapp.coronatest.type.common.DateOfBirthKey import de.rki.coronawarnapp.coronatest.type.isOlderThan21Days import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector import de.rki.coronawarnapp.datadonation.analytics.modules.testresult.AnalyticsTestResultCollector @@ -36,7 +35,7 @@ import timber.log.Timber import javax.inject.Inject @Reusable -class RAProcessor @Inject constructor( +class RATestProcessor @Inject constructor( private val timeStamper: TimeStamper, private val submissionService: CoronaTestService, private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, @@ -56,13 +55,9 @@ class RAProcessor @Inject constructor( analyticsKeySubmissionCollector.reset(type) analyticsTestResultCollector.clear(type) - val dateOfBirthKey = if (request.isDccConsentGiven && request.dateOfBirth != null) { - DateOfBirthKey(request.registrationIdentifier, request.dateOfBirth) - } else null - val serverRequest = RegistrationRequest( key = request.registrationIdentifier, - dateOfBirthKey = dateOfBirthKey, + dateOfBirthKey = null, type = VerificationKeyType.GUID ) @@ -256,7 +251,7 @@ private fun CoronaTestResult.toValidatedResult(): CoronaTestResult { return if (isValid) { this } else { - Timber.tag(RAProcessor.TAG).e("Server returned invalid RapidAntigen testresult $this") + Timber.tag(RATestProcessor.TAG).e("Server returned invalid RapidAntigen testresult $this") RAT_INVALID } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateApiV1.kt index a8ec6a371d59184f11486848504ba2ea034d51ca..58f8f045aa7a1a5e7eae23106243b311593a58af 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateApiV1.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateApiV1.kt @@ -37,5 +37,5 @@ interface CovidCertificateApiV1 { @POST("/version/v1/dcc") suspend fun getComponents( @Body requestBody: ComponentsRequest - ): Response<ComponentsResponse> + ): Response<ComponentsResponse?> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateModule.kt index 16ff646f69a824c39403da866ef1b6bb525d7aca..0cd432e90bfb0fda2fb2445449909204ad2ee6e5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateModule.kt @@ -6,24 +6,44 @@ import dagger.Reusable import de.rki.coronawarnapp.environment.covidcertificate.DCCHttpClient import de.rki.coronawarnapp.environment.covidcertificate.DCCServerUrl import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.lang.reflect.Type @Module class CovidCertificateModule { + /** + * Handles DCC server 202 "retry later" response with 0-byte bodies. + */ + private val nullConverter = object : Converter.Factory() { + fun factoryRef() = this + override fun responseBodyConverter( + type: Type, + annotations: Array<out Annotation>, + retrofit: Retrofit + ) = object : Converter<ResponseBody, Any?> { + val nextConverter = retrofit.nextResponseBodyConverter<Any?>(factoryRef(), type, annotations) + + override fun convert(value: ResponseBody): Any? { + return if (value.contentLength() != 0L) nextConverter.convert(value) else null + } + } + } + @Reusable @Provides fun apiV1( @DCCHttpClient httpClient: OkHttpClient, @DCCServerUrl url: String, gsonConverterFactory: GsonConverterFactory - ): CovidCertificateApiV1 { - return Retrofit.Builder() - .client(httpClient) - .baseUrl(url) - .addConverterFactory(gsonConverterFactory) - .build() - .create(CovidCertificateApiV1::class.java) - } + ): CovidCertificateApiV1 = Retrofit.Builder() + .client(httpClient) + .baseUrl(url) + .addConverterFactory(nullConverter) + .addConverterFactory(gsonConverterFactory) + .build() + .create(CovidCertificateApiV1::class.java) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/ContainerPostProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/ContainerPostProcessor.kt index 6278616309ca033daf51fac2731da3796a834d68..013f39609253a2eb7d58dc36fe130a2c4ecb6f2e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/ContainerPostProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/ContainerPostProcessor.kt @@ -7,8 +7,6 @@ import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import dagger.Reusable -import de.rki.coronawarnapp.coronatest.type.common.TestCertificateContainer -import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCodeExtractor import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor import timber.log.Timber import java.io.IOException @@ -17,7 +15,6 @@ import javax.inject.Inject @Reusable class ContainerPostProcessor @Inject constructor( private val vaccinationQrCodeExtractor: VaccinationQRCodeExtractor, - private val testCertificateQRCodeExtractor: TestCertificateQRCodeExtractor, ) : TypeAdapterFactory { override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> { val delegate = gson.getDelegateAdapter(this, type) @@ -35,10 +32,6 @@ class ContainerPostProcessor @Inject constructor( Timber.v("Injecting VaccinationContainer %s", obj.hashCode()) obj.qrCodeExtractor = vaccinationQrCodeExtractor } - is TestCertificateContainer -> { - Timber.v("Injecting TestCertificateContainer %s", obj.hashCode()) - obj.qrCodeExtractor = testCertificateQRCodeExtractor - } } return obj diff --git a/Corona-Warn-App/src/main/res/xml/network_security_config.xml b/Corona-Warn-App/src/main/res/xml/network_security_config.xml index 00fe5354674c0acbb34ef04bc1121c033c9a3200..ffbf5cd7d82d16014558eba526e7a54009f99789 100644 --- a/Corona-Warn-App/src/main/res/xml/network_security_config.xml +++ b/Corona-Warn-App/src/main/res/xml/network_security_config.xml @@ -1,14 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> -<network-security-config xmlns:tools="http://schemas.android.com/tools"> +<network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">coronawarn.app</domain> <domain includeSubdomains="true">main.px.t-online.de</domain> - <pin-set - expiration="2024-02-12" - tools:ignore="MissingBackupPin"> - <pin digest="SHA-256">c3jf+L8VIAFQnJJDM6Mfb4MtI1JnhVS8JwZHMwJj28M=</pin> - </pin-set> + <!-- FIXME--> + <!-- <pin-set--> + <!-- expiration="2024-02-12"--> + <!-- tools:ignore="MissingBackupPin">--> + <!-- <pin digest="SHA-256">c3jf+L8VIAFQnJJDM6Mfb4MtI1JnhVS8JwZHMwJj28M=</pin>--> + <!-- </pin-set>--> <trust-anchors> <certificates overridePins="false" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt index 267613b4ada2052a2b2e0e5086468ed12c2ba75e..7eb566a58f9810a6458f0c3499b0d2d6243e4a1a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt @@ -6,9 +6,9 @@ import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest -import de.rki.coronawarnapp.coronatest.type.pcr.PCRProcessor +import de.rki.coronawarnapp.coronatest.type.pcr.PCRTestProcessor import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest -import de.rki.coronawarnapp.coronatest.type.rapidantigen.RAProcessor +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RATestProcessor import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery @@ -38,9 +38,9 @@ class CoronaTestRepositoryTest : BaseTest() { registrationToken = "token", testResult = CoronaTestResult.PCR_REDEEMED, ) - @MockK lateinit var pcrProcessor: PCRProcessor + @MockK lateinit var pcrProcessor: PCRTestProcessor - @MockK lateinit var raProcessor: RAProcessor + @MockK lateinit var raProcessor: RATestProcessor private val raTest = RACoronaTest( identifier = "ra-identifier", lastUpdatedAt = Instant.EPOCH, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/TestCertificateRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/TestCertificateRepositoryTest.kt index 2ba03c24600f54faf6e02011c4e6beef59b8a712..00b314260d2ebc0433ce37ca06254b9957680d5a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/TestCertificateRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/TestCertificateRepositoryTest.kt @@ -1,52 +1,35 @@ package de.rki.coronawarnapp.coronatest -import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.appconfig.CovidCertificateConfig import de.rki.coronawarnapp.coronatest.storage.TestCertificateStorage -import de.rki.coronawarnapp.coronatest.type.common.TestCertificateContainer -import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateContainer -import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateServer -import de.rki.coronawarnapp.covidcertificate.server.TestCertificateComponents +import de.rki.coronawarnapp.coronatest.type.common.StoredTestCertificateData +import de.rki.coronawarnapp.coronatest.type.common.TestCertificateProcessor +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateData import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCode import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCodeExtractor -import de.rki.coronawarnapp.util.TimeStamper -import de.rki.coronawarnapp.util.encryption.rsa.RSACryptography -import de.rki.coronawarnapp.util.encryption.rsa.RSAKeyPairGenerator import de.rki.coronawarnapp.vaccination.core.repository.ValueSetsRepository import io.mockk.MockKAnnotations -import io.mockk.Runs import io.mockk.coEvery -import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.just import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flowOf -import okio.ByteString import org.joda.time.Duration import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test import testhelpers.BaseTest import testhelpers.TestDispatcherProvider -import testhelpers.coroutines.runBlockingTest2 class TestCertificateRepositoryTest : BaseTest() { - @MockK lateinit var timeStamper: TimeStamper @MockK lateinit var storage: TestCertificateStorage - @MockK lateinit var certificateServer: CovidCertificateServer - @MockK lateinit var rsaCryptography: RSACryptography @MockK lateinit var qrCodeExtractor: TestCertificateQRCodeExtractor - @MockK lateinit var appConfigProvider: AppConfigProvider - @MockK lateinit var appConfigData: ConfigData @MockK lateinit var covidTestCertificateConfig: CovidCertificateConfig.TestCertificate @MockK lateinit var valueSetsRepository: ValueSetsRepository + @MockK lateinit var testCertificateProcessor: TestCertificateProcessor - private val testCertificateNew = PCRCertificateContainer( + private val testCertificateNew = PCRCertificateData( identifier = "identifier1", registrationToken = "regtoken1", registeredAt = Instant.EPOCH, @@ -58,24 +41,12 @@ class TestCertificateRepositoryTest : BaseTest() { rsaPrivateKey = mockk(), ) - private val testCerticateComponents = mockk<TestCertificateComponents>().apply { - every { dataEncryptionKeyBase64 } returns "dek" - every { encryptedCoseTestCertificateBase64 } returns "" - } - - private var storageSet = mutableSetOf<TestCertificateContainer>() + private var storageSet = mutableSetOf<StoredTestCertificateData>() @BeforeEach fun setup() { MockKAnnotations.init(this) - every { timeStamper.nowUTC } returns Instant.EPOCH - - every { appConfigProvider.currentConfig } returns flowOf(appConfigData) - every { appConfigData.covidCertificateParameters } returns mockk<CovidCertificateConfig>().apply { - every { testCertificate } returns covidTestCertificateConfig - } - covidTestCertificateConfig.apply { every { waitForRetry } returns Duration.standardSeconds(10) every { waitAfterPublicKeyRegistration } returns Duration.standardSeconds(10) @@ -89,13 +60,6 @@ class TestCertificateRepositoryTest : BaseTest() { every { storage.testCertificates } answers { storageSet } } - certificateServer.apply { - coEvery { registerPublicKeyForTest(any(), any()) } just Runs - coEvery { requestCertificateForTest(any()) } returns testCerticateComponents - } - - every { rsaCryptography.decrypt(any(), any()) } returns ByteString.Companion.EMPTY - coEvery { qrCodeExtractor.extract(any(), any()) } returns mockk<TestCertificateQRCode>().apply { every { qrCode } returns "qrCode" every { testCertificateData } returns mockk() @@ -106,38 +70,9 @@ class TestCertificateRepositoryTest : BaseTest() { private fun createInstance(scope: CoroutineScope) = TestCertificateRepository( appScope = scope, dispatcherProvider = TestDispatcherProvider(), - timeStamper = timeStamper, storage = storage, - certificateServer = certificateServer, - rsaKeyPairGenerator = RSAKeyPairGenerator(), - rsaCryptography = rsaCryptography, qrCodeExtractor = qrCodeExtractor, - appConfigProvider = appConfigProvider, valueSetsRepository = valueSetsRepository, + processor = testCertificateProcessor, ) - - @Test - fun `refresh tries public key registration`() = runBlockingTest2(ignoreActive = true) { - storage.testCertificates = setOf(testCertificateNew) - - val instance = createInstance(scope = this) - instance.refresh() - - coVerify { - certificateServer.registerPublicKeyForTest(testCertificateNew.registrationToken, any()) - } - } - - @Test - fun `refresh skips public key registration already registered`() = runBlockingTest2(ignoreActive = true) { - storage.testCertificates = setOf(testCertificateWithPubKey) - - val instance = createInstance(scope = this) - instance.refresh() - - coVerify { - covidTestCertificateConfig.waitAfterPublicKeyRegistration - certificateServer.requestCertificateForTest(testCertificateNew.registrationToken) - } - } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestTestData.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/TestCertificateTestData.kt similarity index 74% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestTestData.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/TestCertificateTestData.kt index 102501c7dd95945ab5cdfccd1f25f535bbe2da2f..0de056a4501d158461578c680059255c9397d278 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestTestData.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/TestCertificateTestData.kt @@ -1,70 +1,30 @@ package de.rki.coronawarnapp.coronatest -import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateContainer -import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACertificateContainer -import de.rki.coronawarnapp.covidcertificate.test.TestCertificateData -import de.rki.coronawarnapp.covidcertificate.test.TestCertificateDccV1 +import de.rki.coronawarnapp.coronatest.type.common.TestCertificateContainer +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateData +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACertificateData import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCodeExtractor import de.rki.coronawarnapp.util.encryption.rsa.RSAKey -import de.rki.coronawarnapp.util.encryption.rsa.RSAKeyPairGenerator -import de.rki.coronawarnapp.vaccination.core.certificate.HealthCertificateHeader import okio.ByteString.Companion.decodeBase64 import org.joda.time.Instant import javax.inject.Inject @Suppress("MaxLineLength") -class CoronaTestTestData @Inject constructor( - private val qrCodeExtractor: TestCertificateQRCodeExtractor, - private val rsaKeyPairGenerator: RSAKeyPairGenerator, +class TestCertificateTestData @Inject constructor( + qrCodeExtractor: TestCertificateQRCodeExtractor ) { - val personATest1CertQRCodeString = "personATest1CertQRCodeString" + val personATest1CertQRCodeString = + "HC1:6BFQ$9FY7$\$Q00019C-5HQ57WDQTLUMS256TUMP49JLZCX/N4VB14NRINX6J7+KF\$UB.N83CMMPVNPAHJ035FT88%AJ1R*UOGR3ZWLC24+F7DO276HB.O*BEVJODVSKNOP0T4\$FZ.R/03N6QK3A05EQVBONNHSJ9WJT+B15H NBEZ3VI8-V77AFL4NY9V*JM *KOPC2H8PR5OG9:VIG$969FJRV28N9PORZJQ43EOIHIJ+83XDDRB201N.L58EB7 8GS8. 9GPNGHFV/6\$I3R3R4930TC/ZGZESM929.D59GO13E\$H7LMUGS18ABF6YD955C C6VPNMZU+3FJR1RVLBZDR2F*8A4MKWIH+/JPU1*H6E2CTHGYHPPU6U*FYFJ6RJO*I8P1T59S:V3V1SA86L8Y8A5XB*10112G8GHK77CBOK9QEI960TTNC.I3A4P8BM6DO:MI/98PC/ZP2:JW3JB.J:R2.5GX0J$1J.OOCYSS8A*DS\$AFVJR9RT-L6N%PS%CV%B8KL0%EL SXLBSA6 %M6JEN+E0Q8A:RAGW16KGUA627-Y2:EDH\$VFVT:29FWUTJSANSYCV4PSKNP\$ZV-8T27SV4CC3CJIV20TKBS22O$/MT/V*0Q0IUE0DBPT+UP03IR1CRFOP3" - val personATest1CertHeader = HealthCertificateHeader( - issuer = "DE", - issuedAt = Instant.parse("2021-05-10T09:25:00.000Z"), - expiresAt = Instant.parse("2022-05-19T09:25:00.000Z"), - ) - - val personATest1Cert = TestCertificateDccV1( - version = "1.0.0", - nameData = TestCertificateDccV1.NameData( - givenName = "Andreas", - givenNameStandardized = "ANDREAS", - familyName = "Astrá Eins", - familyNameStandardized = "ASTRA<EINS", - ), - dob = "1966-11-11", - testCertificateData = listOf( - TestCertificateDccV1.TestCertificateData( - targetId = "840539006", - countryOfTest = "DE", - sc = Instant.EPOCH.toDateTime().toString(), - dr = Instant.EPOCH.toDateTime().toString(), - testCenter = "TODO", - testName = "TODO", - testNameAndManufactor = "TODO", - testResult = "TODO", - testType = "TODO", - certificateIssuer = "Bundesministerium für Gesundheit - Test01", - uniqueCertificateIdentifier = "01DE/00001/1119305005/7T1UG87G61Y7NRXIBQJDTYQ9#S", - ) - ) - ) - - val personATest1CertData = TestCertificateData( - header = personATest1CertHeader, - certificate = personATest1Cert, - ) - - val personATest1CertContainer = run { + val personATest1StoredData = run { val publicKey = RSAKey.Public( "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA2+WCCvy0SNqZMy/V1FYYMkBTGp/5BQt/NxUW1nIkj84u6duqNNQh4GjugoDc8epyl/yi3D61Jt7qArwk+eTcnW4/jEOexT5pCabRKrFm6IMndSefYrP3CeaD86ZU47uhnRuCG3TcPhIqUN2E37EbOsI9Z59JXc5tmmB71CxTF0bjE0PNLgbTU2snnsO6+oz/JLo7D2nw6E9yxSJ8JBjM5j+FC4sYLuO2nYi/BzAGZL/wsKrajg2hjA3f8r1cgst8HdzAJjMUG90pb3UG2K2KVRScbvF8pvRrzLCvJ/gqAGDXX/M00jr407vU8V4O2A9YdSavaC02iRFTNail65cbOW96p3ptjeejofj8l5PO5eBYWERla8NrlD9EcW93+aSmswn4w9iSSq+j38GMyhYulLcOlhKTeWumc5goDjcHyri48Ki70ddGzrxFxggaC/FqlCG85A6/43fVaWH/Wi2uPDPzaRGNQzXRy4LCuE/dvUzp8TlkpcT0QFy/Q4Ke0u1dAgMBAAE\u003d".decodeBase64()!! ) val privateKey = RSAKey.Private( "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".decodeBase64()!! ) - PCRCertificateContainer( + PCRCertificateData( identifier = "identifier", registrationToken = "registrationToken", registeredAt = Instant.ofEpochMilli(12345), @@ -74,58 +34,25 @@ class CoronaTestTestData @Inject constructor( certificateReceivedAt = Instant.ofEpochMilli(123456789), encryptedDataEncryptionkey = "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk=".decodeBase64()!!, testCertificateQrCode = personATest1CertQRCodeString, - ).apply { - preParsedData = personATest1CertData - } + ) } - val personATest2CertQRCodeString = "personATest2CertQRCodeString" - - val personATest2CertHeader = HealthCertificateHeader( - issuer = "DE", - issuedAt = Instant.parse("2021-05-11T09:25:00.000Z"), - expiresAt = Instant.parse("2022-05-11T09:25:00.000Z"), - ) - - val personATest2Cert = TestCertificateDccV1( - version = "1.0.0", - nameData = TestCertificateDccV1.NameData( - givenName = "Andreas", - givenNameStandardized = "ANDREAS", - familyName = "Astrá Eins", - familyNameStandardized = "ASTRA<EINS", - ), - dob = "1966-11-11", - testCertificateData = listOf( - TestCertificateDccV1.TestCertificateData( - targetId = "840539006", - countryOfTest = "DE", - sc = Instant.EPOCH.toDateTime().toString(), - dr = Instant.EPOCH.toDateTime().toString(), - testCenter = "TODO", - testName = "TODO", - testNameAndManufactor = "TODO", - testResult = "TODO", - testType = "TODO", - certificateIssuer = "Bundesministerium für Gesundheit - Test01", - uniqueCertificateIdentifier = "01DE/00001/1119305005/TODO", - ) - ) + val personATest1Container = TestCertificateContainer( + data = personATest1StoredData, + qrCodeExtractor = qrCodeExtractor, ) - val personATest2CertData = TestCertificateData( - header = personATest2CertHeader, - certificate = personATest2Cert, - ) + val personATest2CertQRCodeString = + "HC1:6BFT$9W08+J2DO3L6COM9W\$B8MG9DIV*GZHTGBS46FQ5G+/N1D9K1LV-50OIETJPEU63TT MA5B-+B%95V72UUSRST4662Z8NME.249C1Y*5TIT+TI0QG*%9%VBYBOIZV7UNM%2A8P1N3+IDXN38BO%619B4D515FT*5CR4F:U8M/P-7T%TGX2CDDTB/5GR8:0I$ G*+N0-CIF51925USMI2WX4XKJA0G2XHP268P7ODSZYC4TKJOO:WQ437D:E78V-5UM4LGFBKZ80YIU 8%/GWAIM%KG/VIM2 M4BKP7NI/204CE9BSYP2JGI9GJGQUJ-UEYM7YAQR6E DHZ3-RM3:BQ3KRDMIV5.WVBJ8HT8DG7T GMJ47O2HSA+P6UFGUNIPCNOPFJIPWP8E 8BB0C08F 75N6UW20BA800 8LWN3D NV:U6XRDWF9FOQ1LT+L07BFUBZU37D8ZBP+FBKWO:X1ZK8.0V9-AX02UD5PP8K81+VO*.L4*DJRR7WPEL4G-LYCL+ 9\$TBHFNU0R%GD0/ESSOQH7PZD2 BJA3R74E4WG3W02B67EF8WZN7L0J2*3R5EG0VB7OM2I92W/UMJ7G1 FNSCF O 4W4VSO2DOJUF6I8%10-65UR/JJI3AN2EW-BLRVHQN77J" - val personATest2CertContainer = run { + val personATest2CertStoredData = run { val publicKey = RSAKey.Public( "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnrJ8PbmbOmEEmHB/8yg1bnkUT7jcF4Xfy2Me5imJgLVYQ0cL9UNP91cfFUrgMFV3fHOc0Uuay10TmrBLaEdzqDEZQH4Kj0uZ+hVCtzntVKFviuUoh08wxFlogtc5Sy0NhuTGyC1W/i2AX1SDvet2xMcc1fE44rITQEEAlG8+nfGpbppFHezUOxuZjs9XBTxavDjQyWeFwMD30UAJGakPhOOOj6ihXA19OvQ/tYuYTJ5C9QzeK90C/rYbg2fn+os3EGlb7iJZ2V3KGrNLdMcEtkiG5IiHicaNCn8OS/cI3d29iJE4ECaF711fyF8MG1H2tbkjULS3bsPNUvyvHfM2cjOPRhejayOh+CxQkc3wKar8ApvQCjiVRW05nO0ufHdPMcWJhUlchWYO5mOJTSO8vG/9YqpnTuDc2Gelc4gMK7KATdH3v1FsACPKNJdpt68IfZXgGYn5LtJ7zJB6Yw8Rewj1SaF/wFKXpYd+5JyK18wJTLVYSpiDzidh4DP+R6ZTAgMBAAE\u003d".decodeBase64()!! ) val privateKey = RSAKey.Private( "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".decodeBase64()!! ) - RACertificateContainer( + RACertificateData( identifier = "identifier2", registrationToken = "registrationToken2", registeredAt = Instant.ofEpochMilli(12345), @@ -135,27 +62,33 @@ class CoronaTestTestData @Inject constructor( certificateReceivedAt = Instant.ofEpochMilli(123456789), encryptedDataEncryptionkey = "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk=".decodeBase64()!!, testCertificateQrCode = personATest2CertQRCodeString - ).apply { - preParsedData = personATest2CertData - } + ) } - val personATest3CertContainerNokey = run { - RACertificateContainer( + val personATest2CertContainer = TestCertificateContainer( + data = personATest2CertStoredData, + qrCodeExtractor = qrCodeExtractor, + ) + + val personATest3CertNokeyStoredData = run { + RACertificateData( identifier = "identifier2", registrationToken = "registrationToken2", registeredAt = Instant.ofEpochMilli(12345), ) } - - val personATest4CertContainerPending = run { + val personATest3CertNokeyContainer = TestCertificateContainer( + data = personATest3CertNokeyStoredData, + qrCodeExtractor = qrCodeExtractor, + ) + val personATest4CertPendingStoredData = run { val publicKey = RSAKey.Public( "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnrJ8PbmbOmEEmHB/8yg1bnkUT7jcF4Xfy2Me5imJgLVYQ0cL9UNP91cfFUrgMFV3fHOc0Uuay10TmrBLaEdzqDEZQH4Kj0uZ+hVCtzntVKFviuUoh08wxFlogtc5Sy0NhuTGyC1W/i2AX1SDvet2xMcc1fE44rITQEEAlG8+nfGpbppFHezUOxuZjs9XBTxavDjQyWeFwMD30UAJGakPhOOOj6ihXA19OvQ/tYuYTJ5C9QzeK90C/rYbg2fn+os3EGlb7iJZ2V3KGrNLdMcEtkiG5IiHicaNCn8OS/cI3d29iJE4ECaF711fyF8MG1H2tbkjULS3bsPNUvyvHfM2cjOPRhejayOh+CxQkc3wKar8ApvQCjiVRW05nO0ufHdPMcWJhUlchWYO5mOJTSO8vG/9YqpnTuDc2Gelc4gMK7KATdH3v1FsACPKNJdpt68IfZXgGYn5LtJ7zJB6Yw8Rewj1SaF/wFKXpYd+5JyK18wJTLVYSpiDzidh4DP+R6ZTAgMBAAE\u003d".decodeBase64()!! ) val privateKey = RSAKey.Private( "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".decodeBase64()!! ) - RACertificateContainer( + RACertificateData( identifier = "identifier2", registrationToken = "registrationToken2", registeredAt = Instant.ofEpochMilli(12345), @@ -164,4 +97,9 @@ class CoronaTestTestData @Inject constructor( rsaPrivateKey = privateKey, ) } + + val personATest4CertPendingContainer = TestCertificateContainer( + data = personATest4CertPendingStoredData, + qrCodeExtractor = qrCodeExtractor, + ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorageTest.kt index f26fd0bca0a021a62b96b7ad3e7a6f4681941865..7f29705d8e8f648e4e48cf8b9d715b9b1383f78d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/TestCertificateStorageTest.kt @@ -2,18 +2,18 @@ package de.rki.coronawarnapp.coronatest.storage import android.content.Context import androidx.core.content.edit -import de.rki.coronawarnapp.coronatest.CoronaTestTestData import de.rki.coronawarnapp.coronatest.DaggerCoronaTestTestComponent +import de.rki.coronawarnapp.coronatest.TestCertificateTestData import de.rki.coronawarnapp.util.serialization.SerializationModule import de.rki.coronawarnapp.vaccination.core.repository.storage.ContainerPostProcessor import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.extensions.toComparableJsonPretty import testhelpers.preferences.MockSharedPreferences import javax.inject.Inject @@ -21,7 +21,7 @@ import javax.inject.Inject class TestCertificateStorageTest : BaseTest() { @MockK lateinit var context: Context private lateinit var mockPreferences: MockSharedPreferences - @Inject lateinit var testData: CoronaTestTestData + @Inject lateinit var certificateTestData: TestCertificateTestData @Inject lateinit var postProcessor: ContainerPostProcessor @BeforeEach @@ -63,52 +63,45 @@ class TestCertificateStorageTest : BaseTest() { @Test fun `store two containers, one for each type`() { createInstance().testCertificates = setOf( - testData.personATest1CertContainer, - testData.personATest2CertContainer + certificateTestData.personATest1StoredData, + certificateTestData.personATest2CertStoredData ) -// (mockPreferences.dataMapPeek["testcertificate.data.pcr"] as String).toComparableJsonPretty() shouldBe """ -// [ -// { -// "identifier": "identifier", -// "registrationToken": "registrationToken", -// "registeredAt": 12345, -// "publicKeyRegisteredAt": 6789, -// "rsaPublicKey": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA2+WCCvy0SNqZMy/V1FYYMkBTGp/5BQt/NxUW1nIkj84u6duqNNQh4GjugoDc8epyl/yi3D61Jt7qArwk+eTcnW4/jEOexT5pCabRKrFm6IMndSefYrP3CeaD86ZU47uhnRuCG3TcPhIqUN2E37EbOsI9Z59JXc5tmmB71CxTF0bjE0PNLgbTU2snnsO6+oz/JLo7D2nw6E9yxSJ8JBjM5j+FC4sYLuO2nYi/BzAGZL/wsKrajg2hjA3f8r1cgst8HdzAJjMUG90pb3UG2K2KVRScbvF8pvRrzLCvJ/gqAGDXX/M00jr407vU8V4O2A9YdSavaC02iRFTNail65cbOW96p3ptjeejofj8l5PO5eBYWERla8NrlD9EcW93+aSmswn4w9iSSq+j38GMyhYulLcOlhKTeWumc5goDjcHyri48Ki70ddGzrxFxggaC/FqlCG85A6/43fVaWH/Wi2uPDPzaRGNQzXRy4LCuE/dvUzp8TlkpcT0QFy/Q4Ke0u1dAgMBAAE\u003d", -// "rsaPrivateKey": "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", -// "certificateReceivedAt": 123456789, -// "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d", -// "testCertificateQrCode": "personATest1CertQRCodeString" -// } -// ] -// """.toComparableJsonPretty() -// -// (mockPreferences.dataMapPeek["testcertificate.data.ra"] as String).toComparableJsonPretty() shouldBe """ -// [ -// { -// "identifier": "identifier2", -// "registrationToken": "registrationToken2", -// "registeredAt": 12345, -// "publicKeyRegisteredAt": 6789, -// "rsaPublicKey": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnrJ8PbmbOmEEmHB/8yg1bnkUT7jcF4Xfy2Me5imJgLVYQ0cL9UNP91cfFUrgMFV3fHOc0Uuay10TmrBLaEdzqDEZQH4Kj0uZ+hVCtzntVKFviuUoh08wxFlogtc5Sy0NhuTGyC1W/i2AX1SDvet2xMcc1fE44rITQEEAlG8+nfGpbppFHezUOxuZjs9XBTxavDjQyWeFwMD30UAJGakPhOOOj6ihXA19OvQ/tYuYTJ5C9QzeK90C/rYbg2fn+os3EGlb7iJZ2V3KGrNLdMcEtkiG5IiHicaNCn8OS/cI3d29iJE4ECaF711fyF8MG1H2tbkjULS3bsPNUvyvHfM2cjOPRhejayOh+CxQkc3wKar8ApvQCjiVRW05nO0ufHdPMcWJhUlchWYO5mOJTSO8vG/9YqpnTuDc2Gelc4gMK7KATdH3v1FsACPKNJdpt68IfZXgGYn5LtJ7zJB6Yw8Rewj1SaF/wFKXpYd+5JyK18wJTLVYSpiDzidh4DP+R6ZTAgMBAAE\u003d", -// "rsaPrivateKey": "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", -// "certificateReceivedAt": 123456789, -// "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d", -// "testCertificateQrCode": "personATest2CertQRCodeString" -// } -// ] -// """.toComparableJsonPretty() + (mockPreferences.dataMapPeek["testcertificate.data.pcr"] as String).toComparableJsonPretty() shouldBe """ + [ + { + "identifier": "identifier", + "registrationToken": "registrationToken", + "registeredAt": 12345, + "publicKeyRegisteredAt": 6789, + "rsaPublicKey": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA2+WCCvy0SNqZMy/V1FYYMkBTGp/5BQt/NxUW1nIkj84u6duqNNQh4GjugoDc8epyl/yi3D61Jt7qArwk+eTcnW4/jEOexT5pCabRKrFm6IMndSefYrP3CeaD86ZU47uhnRuCG3TcPhIqUN2E37EbOsI9Z59JXc5tmmB71CxTF0bjE0PNLgbTU2snnsO6+oz/JLo7D2nw6E9yxSJ8JBjM5j+FC4sYLuO2nYi/BzAGZL/wsKrajg2hjA3f8r1cgst8HdzAJjMUG90pb3UG2K2KVRScbvF8pvRrzLCvJ/gqAGDXX/M00jr407vU8V4O2A9YdSavaC02iRFTNail65cbOW96p3ptjeejofj8l5PO5eBYWERla8NrlD9EcW93+aSmswn4w9iSSq+j38GMyhYulLcOlhKTeWumc5goDjcHyri48Ki70ddGzrxFxggaC/FqlCG85A6/43fVaWH/Wi2uPDPzaRGNQzXRy4LCuE/dvUzp8TlkpcT0QFy/Q4Ke0u1dAgMBAAE\u003d", + "rsaPrivateKey": "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", + "certificateReceivedAt": 123456789, + "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d", + "testCertificateQrCode": "${certificateTestData.personATest1CertQRCodeString}" + } + ] + """.toComparableJsonPretty() + + (mockPreferences.dataMapPeek["testcertificate.data.ra"] as String).toComparableJsonPretty() shouldBe """ + [ + { + "identifier": "identifier2", + "registrationToken": "registrationToken2", + "registeredAt": 12345, + "publicKeyRegisteredAt": 6789, + "rsaPublicKey": "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnrJ8PbmbOmEEmHB/8yg1bnkUT7jcF4Xfy2Me5imJgLVYQ0cL9UNP91cfFUrgMFV3fHOc0Uuay10TmrBLaEdzqDEZQH4Kj0uZ+hVCtzntVKFviuUoh08wxFlogtc5Sy0NhuTGyC1W/i2AX1SDvet2xMcc1fE44rITQEEAlG8+nfGpbppFHezUOxuZjs9XBTxavDjQyWeFwMD30UAJGakPhOOOj6ihXA19OvQ/tYuYTJ5C9QzeK90C/rYbg2fn+os3EGlb7iJZ2V3KGrNLdMcEtkiG5IiHicaNCn8OS/cI3d29iJE4ECaF711fyF8MG1H2tbkjULS3bsPNUvyvHfM2cjOPRhejayOh+CxQkc3wKar8ApvQCjiVRW05nO0ufHdPMcWJhUlchWYO5mOJTSO8vG/9YqpnTuDc2Gelc4gMK7KATdH3v1FsACPKNJdpt68IfZXgGYn5LtJ7zJB6Yw8Rewj1SaF/wFKXpYd+5JyK18wJTLVYSpiDzidh4DP+R6ZTAgMBAAE\u003d", + "rsaPrivateKey": "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", + "certificateReceivedAt": 123456789, + "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d", + "testCertificateQrCode": "${certificateTestData.personATest2CertQRCodeString}" + } + ] + """.toComparableJsonPretty() createInstance().testCertificates shouldBe setOf( - testData.personATest1CertContainer, - testData.personATest2CertContainer + certificateTestData.personATest1StoredData, + certificateTestData.personATest2CertStoredData ) } - - @Test - fun `post processor injects data extractors`() { - createInstance().testCertificates = setOf(testData.personATest1CertContainer) - - createInstance().testCertificates.single().qrCodeExtractor shouldNotBe null - } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/TestCertificateContainerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/TestCertificateContainerTest.kt index 6459f54ed4d8b69fff4ce8a45ab90e996d913ee8..b50001b929e9254d01120308f3d4b691ce1d829a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/TestCertificateContainerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/TestCertificateContainerTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.coronatest.type -import de.rki.coronawarnapp.coronatest.CoronaTestTestData import de.rki.coronawarnapp.coronatest.DaggerCoronaTestTestComponent +import de.rki.coronawarnapp.coronatest.TestCertificateTestData import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.mockk.mockk @@ -13,7 +13,7 @@ import javax.inject.Inject class TestCertificateContainerTest : BaseTest() { - @Inject lateinit var testData: CoronaTestTestData + @Inject lateinit var certificateTestData: TestCertificateTestData @BeforeEach fun setup() { @@ -22,33 +22,33 @@ class TestCertificateContainerTest : BaseTest() { @Test fun `ui facing test certificate creation and fallbacks`() { - testData.personATest2CertContainer.apply { + certificateTestData.personATest2CertContainer.apply { isPublicKeyRegistered shouldBe true isCertificateRetrievalPending shouldBe false - certificateId shouldBe "01DE/00001/1119305005/TODO" - testCertificateQrCode shouldBe "personATest2CertQRCodeString" - certificateReceivedAt shouldBe Instant.parse("1970-01-02T10:17:36.789Z") + certificateId shouldBe "URN:UVCI:V1:DE:7WR8CE12Y8O2AN4NK320TPNKB1" + data.testCertificateQrCode shouldBe certificateTestData.personATest2CertQRCodeString + data.certificateReceivedAt shouldBe Instant.parse("1970-01-02T10:17:36.789Z") toTestCertificate(null) shouldNotBe null } } @Test fun `pending check and nullability`() { - testData.personATest3CertContainerNokey.apply { + certificateTestData.personATest3CertNokeyContainer.apply { isPublicKeyRegistered shouldBe false isCertificateRetrievalPending shouldBe true certificateId shouldBe null - testCertificateQrCode shouldBe null - certificateReceivedAt shouldBe null + data.testCertificateQrCode shouldBe null + data.certificateReceivedAt shouldBe null toTestCertificate(mockk()) shouldBe null } - testData.personATest4CertContainerPending.apply { + certificateTestData.personATest4CertPendingContainer.apply { isPublicKeyRegistered shouldBe true isCertificateRetrievalPending shouldBe true certificateId shouldBe null - testCertificateQrCode shouldBe null - certificateReceivedAt shouldBe null + data.testCertificateQrCode shouldBe null + data.certificateReceivedAt shouldBe null toTestCertificate(mockk()) shouldBe null } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateProcessorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b409eb693632a06370a709c6c8ed95439b1ca6cc --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/common/TestCertificateProcessorTest.kt @@ -0,0 +1,118 @@ +package de.rki.coronawarnapp.coronatest.type.common + +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.appconfig.CovidCertificateConfig +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCertificateData +import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateServer +import de.rki.coronawarnapp.covidcertificate.server.TestCertificateComponents +import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCode +import de.rki.coronawarnapp.covidcertificate.test.TestCertificateQRCodeExtractor +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.encryption.rsa.RSACryptography +import de.rki.coronawarnapp.util.encryption.rsa.RSAKeyPairGenerator +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import okio.ByteString +import org.joda.time.Duration +import org.joda.time.Instant +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.coroutines.runBlockingTest2 + +class TestCertificateProcessorTest : BaseTest() { + + @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var certificateServer: CovidCertificateServer + @MockK lateinit var rsaCryptography: RSACryptography + @MockK lateinit var qrCodeExtractor: TestCertificateQRCodeExtractor + @MockK lateinit var appConfigProvider: AppConfigProvider + @MockK lateinit var appConfigData: ConfigData + @MockK lateinit var covidTestCertificateConfig: CovidCertificateConfig.TestCertificate + + private val testCertificateNew = PCRCertificateData( + identifier = "identifier1", + registrationToken = "regtoken1", + registeredAt = Instant.EPOCH, + ) + + private val testCertificateWithPubKey = testCertificateNew.copy( + publicKeyRegisteredAt = Instant.EPOCH, + rsaPublicKey = mockk(), + rsaPrivateKey = mockk(), + ) + + private val testCerticateComponents = mockk<TestCertificateComponents>().apply { + every { dataEncryptionKeyBase64 } returns "dek" + every { encryptedCoseTestCertificateBase64 } returns "" + } + + private var storageSet = mutableSetOf<StoredTestCertificateData>() + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { timeStamper.nowUTC } returns Instant.EPOCH + + every { appConfigProvider.currentConfig } returns flowOf(appConfigData) + every { appConfigData.covidCertificateParameters } returns mockk<CovidCertificateConfig>().apply { + every { testCertificate } returns covidTestCertificateConfig + } + + covidTestCertificateConfig.apply { + every { waitForRetry } returns Duration.standardSeconds(10) + every { waitAfterPublicKeyRegistration } returns Duration.standardSeconds(10) + } + + certificateServer.apply { + coEvery { registerPublicKeyForTest(any(), any()) } just Runs + coEvery { requestCertificateForTest(any()) } returns testCerticateComponents + } + + every { rsaCryptography.decrypt(any(), any()) } returns ByteString.Companion.EMPTY + + coEvery { qrCodeExtractor.extract(any(), any()) } returns mockk<TestCertificateQRCode>().apply { + every { qrCode } returns "qrCode" + every { testCertificateData } returns mockk() + } + } + + private fun createInstance() = TestCertificateProcessor( + qrCodeExtractor = qrCodeExtractor, + timeStamper = timeStamper, + certificateServer = certificateServer, + rsaKeyPairGenerator = RSAKeyPairGenerator(), + rsaCryptography = rsaCryptography, + appConfigProvider = appConfigProvider, + ) + + @Test + fun `public key registration`() = runBlockingTest2(ignoreActive = true) { + val instance = createInstance() + instance.registerPublicKey(testCertificateNew) + + coVerify { + certificateServer.registerPublicKeyForTest(testCertificateNew.registrationToken, any()) + } + } + + @Test + fun `obtain certificate components`() = runBlockingTest2(ignoreActive = true) { + val instance = createInstance() + instance.obtainCertificate(testCertificateWithPubKey) + + coVerify { + covidTestCertificateConfig.waitAfterPublicKeyRegistration + certificateServer.requestCertificateForTest(testCertificateNew.registrationToken) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt index 5a59b24045cfedeca860fc2983bb76600da89466..04e5a7110f6f46ccf7ebac591793fc89b0892b98 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt @@ -100,7 +100,7 @@ class PCRProcessorTest : BaseTest() { runBlocking { PcrTeleTanCensor.clearTans() } } - fun createInstance() = PCRProcessor( + fun createInstance() = PCRTestProcessor( timeStamper = timeStamper, submissionService = submissionService, analyticsKeySubmissionCollector = analyticsKeySubmissionCollector, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt similarity index 94% rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt index 8d3affbb2c77dad1f6a26825caf6523663d1de28..2846d0f54b9f17b104b72c59e83d4295f94f8f4f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt @@ -16,6 +16,7 @@ import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.values import de.rki.coronawarnapp.coronatest.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.server.RegistrationData import de.rki.coronawarnapp.coronatest.server.RegistrationRequest +import de.rki.coronawarnapp.coronatest.server.VerificationKeyType import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN import de.rki.coronawarnapp.coronatest.type.CoronaTestService import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector @@ -26,6 +27,7 @@ import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just @@ -37,7 +39,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest -class RapidAntigenProcessorTest : BaseTest() { +class RAProcessorTest : BaseTest() { @MockK lateinit var timeStamper: TimeStamper @MockK lateinit var submissionService: CoronaTestService @@ -95,7 +97,7 @@ class RapidAntigenProcessorTest : BaseTest() { } } - fun createInstance() = RAProcessor( + fun createInstance() = RATestProcessor( timeStamper = timeStamper, submissionService = submissionService, analyticsKeySubmissionCollector = analyticsKeySubmissionCollector, @@ -141,6 +143,27 @@ class RapidAntigenProcessorTest : BaseTest() { instance.pollServer(past60DaysTest).testResult shouldBe RAT_REDEEMED } + @Test + fun `registering a new test`() = runBlockingTest { + val request = CoronaTestQRCode.RapidAntigen( + hash = "hash", + createdAt = Instant.EPOCH, + ) + + val instance = createInstance() + instance.create(request) + + val expectedServerRequest = RegistrationRequest( + key = request.registrationIdentifier, + type = VerificationKeyType.GUID, + dateOfBirthKey = null, + ) + + coVerify { + submissionService.registerTest(expectedServerRequest) + } + } + @Test fun `registering a new test maps invalid results to INVALID state`() = runBlockingTest { var registrationData = RegistrationData(