Skip to content
Snippets Groups Projects
Unverified Commit 4d08ecc3 authored by Kolya Opahle's avatar Kolya Opahle Committed by GitHub
Browse files

Vaccination Certificate - Censor Logs (EXPOSUREAPP-7097) (#3173)


* Implemented CertificateQrCodeCensor + CertificateQrCodeCensorTest also added setup calls in places where logging data is generated

* Adjust censoring to allow for multiple datasets.

* Simplify censor data gathering.

Co-authored-by: default avatarharambasicluka <64483219+harambasicluka@users.noreply.github.com>
Co-authored-by: default avatarChilja Gossow <49635654+chiljamgossow@users.noreply.github.com>
Co-authored-by: default avatarMatthias Urhahn <matthias.urhahn@sap.com>
parent 34663897
No related branches found
No related tags found
No related merge requests found
Showing
with 310 additions and 6 deletions
......@@ -16,6 +16,7 @@ import de.rki.coronawarnapp.bugreporting.censors.submission.PcrQrCodeCensor
import de.rki.coronawarnapp.bugreporting.censors.submission.RACoronaTestCensor
import de.rki.coronawarnapp.bugreporting.censors.submission.RatProfileCensor
import de.rki.coronawarnapp.bugreporting.censors.submission.RatQrCodeCensor
import de.rki.coronawarnapp.bugreporting.censors.vaccination.CertificateQrCodeCensor
import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLoggerScope
import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope
import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.LogUploadApiV1
......@@ -117,4 +118,8 @@ class BugReportingSharedModule {
@Provides
@IntoSet
fun ratProfileCensor(censor: RatProfileCensor): BugCensor = censor
@Provides
@IntoSet
fun certificateQrCodeCensor(censor: CertificateQrCodeCensor): BugCensor = censor
}
package de.rki.coronawarnapp.bugreporting.censors.vaccination
import dagger.Reusable
import de.rki.coronawarnapp.bugreporting.censors.BugCensor
import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNewLogLineIfDifferent
import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1
import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData
import java.util.LinkedList
import javax.inject.Inject
@Reusable
class CertificateQrCodeCensor @Inject constructor() : BugCensor {
override suspend fun checkLog(entry: LogLine): LogLine? {
var newMessage = entry.message
synchronized(qrCodeStringsToCensor) { qrCodeStringsToCensor.toList() }.forEach {
newMessage = newMessage.replace(
it,
PLACEHOLDER + it.takeLast(4)
)
}
synchronized(certsToCensor) { certsToCensor.toList() }.forEach {
it.certificate.apply {
newMessage = newMessage.replace(
dob,
"vaccinationCertificate/dob"
)
newMessage = newMessage.replace(
dateOfBirth.toString(),
"vaccinationCertificate/dateOfBirth"
)
newMessage = censorNameData(nameData, newMessage)
vaccinationDatas.forEach { data ->
newMessage = censorVaccinationData(data, newMessage)
}
}
}
return entry.toNewLogLineIfDifferent(newMessage)
}
private fun censorVaccinationData(
vaccinationData: VaccinationDGCV1.VaccinationData,
message: String
): String {
var newMessage = message
newMessage = newMessage.replace(
vaccinationData.dt,
"vaccinationData/dt"
)
newMessage = newMessage.replace(
vaccinationData.marketAuthorizationHolderId,
"vaccinationData/marketAuthorizationHolderId"
)
newMessage = newMessage.replace(
vaccinationData.medicalProductId,
"vaccinationData/medicalProductId"
)
newMessage = newMessage.replace(
vaccinationData.targetId,
"vaccinationData/targetId"
)
newMessage = newMessage.replace(
vaccinationData.certificateIssuer,
"vaccinationData/certificateIssuer"
)
newMessage = newMessage.replace(
vaccinationData.uniqueCertificateIdentifier,
"vaccinationData/uniqueCertificateIdentifier"
)
newMessage = newMessage.replace(
vaccinationData.countryOfVaccination,
"vaccinationData/countryOfVaccination"
)
newMessage = newMessage.replace(
vaccinationData.vaccineId,
"vaccinationData/vaccineId"
)
newMessage = newMessage.replace(
vaccinationData.vaccinatedAt.toString(),
"vaccinationData/vaccinatedAt"
)
return newMessage
}
private fun censorNameData(nameData: VaccinationDGCV1.NameData, message: String): String {
var newMessage = message
nameData.familyName?.let { fName ->
newMessage = newMessage.replace(
fName,
"nameData/familyName"
)
}
newMessage = newMessage.replace(
nameData.familyNameStandardized,
"nameData/familyNameStandardized"
)
nameData.givenName?.let { gName ->
newMessage = newMessage.replace(
gName,
"nameData/givenName"
)
}
nameData.givenNameStandardized?.let { gName ->
newMessage = newMessage.replace(
gName,
"nameData/givenNameStandardized"
)
}
return newMessage
}
companion object {
private val qrCodeStringsToCensor = LinkedList<String>()
fun addQRCodeStringToCensor(rawString: String) = synchronized(qrCodeStringsToCensor) {
qrCodeStringsToCensor.apply {
if (contains(rawString)) return@apply
addFirst(rawString)
// Max certs is at 4, but we may scan invalid qr codes that are not added which will be shown in raw
if (size > 8) removeLast()
}
}
fun clearQRCodeStringToCensor() = synchronized(qrCodeStringsToCensor) { qrCodeStringsToCensor.clear() }
private val certsToCensor = LinkedList<VaccinationCertificateData>()
fun addCertificateToCensor(cert: VaccinationCertificateData) = synchronized(certsToCensor) {
certsToCensor.apply {
if (contains(cert)) return@apply
addFirst(cert)
// max certs we should have is 2, 50% leeway
if (size > 4) removeLast()
}
}
fun clearCertificateToCensor() = synchronized(certsToCensor) { certsToCensor.clear() }
private const val PLACEHOLDER = "########-####-####-####-########"
}
}
......@@ -14,7 +14,7 @@ class CoronaTestQrCodeValidator @Inject constructor(
fun validate(rawString: String): CoronaTestQRCode {
return findExtractor(rawString)
?.extract(rawString)
?.also { Timber.i("Extracted data from QR code is $it") }
?.also { Timber.i("Extracted data from QR code is %s", it) }
?: throw InvalidQRCodeException()
}
......
package de.rki.coronawarnapp.vaccination.core.qrcode
import de.rki.coronawarnapp.bugreporting.censors.vaccination.CertificateQrCodeCensor
import de.rki.coronawarnapp.coronatest.qrcode.QrCodeExtractor
import de.rki.coronawarnapp.util.compression.inflate
import de.rki.coronawarnapp.util.encoding.Base45Decoder
......@@ -22,6 +23,8 @@ class VaccinationQRCodeExtractor @Inject constructor(
override fun canHandle(rawString: String): Boolean = rawString.startsWith(PREFIX)
override fun extract(rawString: String): VaccinationCertificateQRCode {
CertificateQrCodeCensor.addQRCodeStringToCensor(rawString)
val parsedData = rawString
.removePrefix(PREFIX)
.decodeBase45()
......@@ -56,6 +59,8 @@ class VaccinationQRCodeExtractor @Inject constructor(
header = headerParser.parse(cbor),
certificate = bodyParser.parse(cbor)
).also {
CertificateQrCodeCensor.addCertificateToCensor(it)
}.also {
Timber.v("Parsed vaccination certificate for %s", it.certificate.nameData.familyNameStandardized)
}
}
......
......@@ -14,9 +14,11 @@ class VaccinationQRCodeValidator @Inject constructor(
private val extractors = setOf(vaccinationQRCodeExtractor)
fun validate(rawString: String): VaccinationCertificateQRCode {
// If there is more than one "extractor" in the future, check censoring again.
// CertificateQrCodeCensor.addQRCodeStringToCensor(rawString)
return findExtractor(rawString)
?.extract(rawString)
?.also { Timber.i("Extracted data from QR code is $it") }
?.also { Timber.i("Extracted data from QR code is %s", it) }
?: throw InvalidHealthCertificateException(VC_PREFIX_INVALID)
}
......
......@@ -32,7 +32,7 @@ data class VaccinationContainer internal constructor(
constructor() : this("", Instant.EPOCH)
@delegate:Transient
private val certificateData: VaccinationCertificateData by lazy {
internal val certificateData: VaccinationCertificateData by lazy {
preParsedData ?: qrCodeExtractor.extract(vaccinationQrCode).parsedData
}
......
......@@ -36,9 +36,9 @@ class VaccinationStorage @Inject constructor(
return@mapNotNull null
}
value as String
gson.fromJson<VaccinatedPersonData>(value).also {
Timber.tag(TAG).v("Person loaded: %s", it)
requireNotNull(it.identifier)
gson.fromJson<VaccinatedPersonData>(value).also { personData ->
Timber.tag(TAG).v("Person loaded: %s", personData)
requireNotNull(personData.identifier)
}
}
return persons.toSet()
......
package de.rki.coronawarnapp.bugreporting.censors.vaccination
import de.rki.coronawarnapp.bugreporting.debuglog.LogLine
import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1
import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations
import io.mockk.mockk
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
internal class CertificateQrCodeCensorTest {
private val testRawString =
"HC1:6BFOXN*TS0BI\$ZD.P9UOL97O4-2HH77HRM3DSPTLRR+%3.ZH9M9ESIGUBA KWML/O6HXK 0D+4O5VC9:BPCNYKMXEE1JAA/CZIK0JK1WL260X638J3-E3GG396B-43FZT-43:S0X37*ZV+FNI6HXY0ZSVILVQJF//05MVZJ5V.499TXY9KK9+OC+G9QJPNF67J6QW67KQY466PPM4MLJE+.PDB9L6Q2+PFQ5DB96PP5/P-59A%N+892 7J235II3NJ7PK7SLQMIJSBHVA7UJQWT.+S+ND%%M%331BH.IA.C8KRDL4O54O4IGUJKJGI0JAXD15IAXMFU*GSHGHD63DAOC9JU0H11+*4.\$S6ZC0JBZAB-C3QHISKE MCAOI8%M3V96-PY\$N6XOWLIBPIAYU:*JIRHUF2XZQ4H9 XJ72WG1K36VF/9BL56%E8T1OEEG%5TW5A 6YO67N6UCE:WT6BT-UMM:ABJK2TMDN1:FW-%T+\$D78NDSC3%5F61NYS-P9LOE0%J/ZAY:N5L4H-H/LH:AO3FU JHG7K46IOIMT.RE%PHLA21JRI3HTC\$AH"
private val testCertificateData = VaccinationCertificateData(
header = mockk(),
certificate = VaccinationDGCV1(
version = "1",
nameData = VaccinationDGCV1.NameData(
familyName = "Kevin",
familyNameStandardized = "Kevin2",
givenName = "Bob",
givenNameStandardized = "Bob2"
),
dob = "1969-11-16",
vaccinationDatas = listOf(
VaccinationDGCV1.VaccinationData(
targetId = "12345",
vaccineId = "1214765",
medicalProductId = "aaEd/easd",
marketAuthorizationHolderId = "ASD-2312",
doseNumber = 2,
totalSeriesOfDoses = 5,
dt = "1969-04-20",
countryOfVaccination = "DE",
certificateIssuer = "Herbert",
uniqueCertificateIdentifier = "urn:uvci:01:NL:PlA8UWS60Z4RZXVALl6GAZ"
)
)
)
)
@BeforeEach
fun setUp() {
MockKAnnotations.init(this)
}
@AfterEach
fun teardown() {
CertificateQrCodeCensor.clearCertificateToCensor()
CertificateQrCodeCensor.clearQRCodeStringToCensor()
}
private fun createInstance() = CertificateQrCodeCensor()
@Test
fun `checkLog() should return censored LogLine`() = runBlockingTest {
CertificateQrCodeCensor.addQRCodeStringToCensor(testRawString)
CertificateQrCodeCensor.addCertificateToCensor(testCertificateData)
val censor = createInstance()
val logLineToCensor = LogLine(
timestamp = 1,
priority = 3,
message = "Here comes the rawString: $testRawString of the vaccine certificate",
tag = "I am tag",
throwable = null
)
censor.checkLog(logLineToCensor) shouldBe logLineToCensor.copy(
message = "Here comes the rawString: ########-####-####-####-########C\$AH of the vaccine certificate",
)
val certDataToCensor = LogLine(
timestamp = 1,
priority = 3,
message = "Hello my name is Kevin Bob, i was born at 1969-11-16, i have been " +
"vaccinated with: 12345 1214765 aaEd/easd ASD-2312 1969-04-20 DE Herbert" +
" urn:uvci:01:NL:PlA8UWS60Z4RZXVALl6GAZ",
tag = "I am tag",
throwable = null
)
censor.checkLog(certDataToCensor) shouldBe certDataToCensor.copy(
message = "Hello my name is nameData/familyName nameData/givenName, i was born at " +
"vaccinationCertificate/dob, i have been vaccinated with: vaccinationData/targetId " +
"vaccinationData/vaccineId vaccinationData/medicalProductId" +
" vaccinationData/marketAuthorizationHolderId vaccinationData/dt" +
" vaccinationData/countryOfVaccination vaccinationData/certificateIssuer" +
" vaccinationData/uniqueCertificateIdentifier"
)
}
@Test
fun `checkLog() should return null if no data to censor was set`() = runBlockingTest {
val censor = createInstance()
val logLineNotToCensor = LogLine(
timestamp = 1,
priority = 3,
message = "Here comes the rawData: $testRawString",
tag = "I am tag",
throwable = null
)
censor.checkLog(logLineNotToCensor) shouldBe null
}
@Test
fun `checkLog() should return null if nothing should be censored`() = runBlockingTest {
CertificateQrCodeCensor.addQRCodeStringToCensor(testRawString.replace("1", "2"))
CertificateQrCodeCensor.addCertificateToCensor(testCertificateData)
val censor = createInstance()
val logLineNotToCensor = LogLine(
timestamp = 1,
priority = 3,
message = "Here comes the rawString: $testRawString",
tag = "I am tag",
throwable = null
)
censor.checkLog(logLineNotToCensor) shouldBe null
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment