diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/ZLIBCompression.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/ZLIBCompression.kt index 0c585d8bb3c30561a85492713a9467bf89c257b2..d03ef22a3372422c6651b5b12f62c12fdb4fa182 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/ZLIBCompression.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/compression/ZLIBCompression.kt @@ -1,14 +1,13 @@ package de.rki.coronawarnapp.util.compression import okio.Buffer -import okio.ByteString import okio.inflate import java.util.zip.Inflater import javax.inject.Inject class ZLIBCompression @Inject constructor() { @Suppress("NestedBlockDepth") - fun decompress(input: ByteString, sizeLimit: Long = -1L): ByteString = try { + fun decompress(input: ByteArray, sizeLimit: Long = -1L): ByteArray = try { val inflaterSource = input.let { val buffer = Buffer().write(it) buffer.inflate(Inflater()) @@ -24,10 +23,10 @@ class ZLIBCompression @Inject constructor() { } } - sink.readByteString() + sink.readByteArray() } catch (e: Throwable) { throw InvalidInputException("ZLIB decompression failed.", e) } } -fun ByteString.inflate(sizeLimit: Long = -1L) = ZLIBCompression().decompress(this, sizeLimit) +fun ByteArray.inflate(sizeLimit: Long = -1L) = ZLIBCompression().decompress(this, sizeLimit) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encoding/Base45Extensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encoding/Base45Extensions.kt index aeddd2010e19329da9e2e337a899a1a5dccd90ed..9edb3c513aecd78e1ec6da3c000aecc5161c246b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encoding/Base45Extensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encoding/Base45Extensions.kt @@ -1,16 +1,13 @@ package de.rki.coronawarnapp.util.encoding -import okio.ByteString -import okio.ByteString.Companion.toByteString - /** - * Decodes [String] into [ByteString] using Base45 decoder - * @return [ByteString] + * Decodes [String] into [ByteArray] using Base45 decoder + * @return [ByteArray] */ -fun String.decodeBase45(): ByteString = Base45Decoder.decode(this).toByteString() +fun String.decodeBase45(): ByteArray = Base45Decoder.decode(this) /** - * Encodes [ByteString] into base45 [String] + * Encodes [ByteArray] into base45 [String] * @return [String] */ -fun ByteString.base45(): String = Base45Decoder.encode(this.toByteArray()) +fun ByteArray.base45(): String = Base45Decoder.encode(this) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinationCertificate.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinationCertificate.kt index 71737e4627f3a185e671b8bdcd8f5bb1b8b87f56..98c5018ec7464e0c8301d8cbc1f3287bba641d5e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinationCertificate.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/VaccinationCertificate.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.vaccination.core import de.rki.coronawarnapp.ui.Country +import de.rki.coronawarnapp.vaccination.core.qrcode.QrCodeString import org.joda.time.Instant import org.joda.time.LocalDate @@ -27,4 +28,6 @@ interface VaccinationCertificate { val issuer: String val issuedAt: Instant val expiresAt: Instant + + val vaccinationQrCodeString: QrCodeString } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/HealthCertificateCOSEDecoder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/HealthCertificateCOSEDecoder.kt index af6a7aaf0781b977e6c40570f0842d80d19e68f8..fbb291dcd3dee0f8be7c67f9d11813b453d6727b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/HealthCertificateCOSEDecoder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/HealthCertificateCOSEDecoder.kt @@ -9,7 +9,7 @@ import javax.inject.Inject class HealthCertificateCOSEDecoder @Inject constructor() { fun decode(input: RawCOSEObject): CBORObject = try { - val messageObject = CBORObject.DecodeFromBytes(input.asByteArray).validate() + val messageObject = CBORObject.DecodeFromBytes(input).validate() val content = messageObject[2].GetByteString() CBORObject.DecodeFromBytes(content) } catch (e: InvalidHealthCertificateException) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/RawCOSEObject.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/RawCOSEObject.kt index 28fdf8f2a2362233297bf952100920bab351181e..0a13bb0971b82d0456b0539469d6e0e63a16d690 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/RawCOSEObject.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/RawCOSEObject.kt @@ -1,82 +1,3 @@ package de.rki.coronawarnapp.vaccination.core.certificate -import com.google.gson.JsonParseException -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.RequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.ResponseBody -import okio.ByteString -import okio.ByteString.Companion.decodeBase64 -import okio.ByteString.Companion.toByteString -import org.json.JSONObject -import retrofit2.Converter -import retrofit2.Retrofit -import java.lang.reflect.Type -import javax.inject.Inject - -data class RawCOSEObject( - val data: ByteString -) { - constructor(data: ByteArray) : this(data.toByteString()) - - val asByteArray: ByteArray - get() = data.toByteArray() - - companion object { - val EMPTY = RawCOSEObject(data = ByteString.EMPTY) - } - - class JsonAdapter : TypeAdapter<RawCOSEObject>() { - override fun write(out: JsonWriter, value: RawCOSEObject?) { - if (value == null) out.nullValue() - else value.data.base64().let { out.value(it) } - } - - override fun read(reader: JsonReader): RawCOSEObject? = when (reader.peek()) { - JSONObject.NULL -> reader.nextNull().let { null } - else -> { - val raw = reader.nextString() - raw.decodeBase64()?.let { RawCOSEObject(data = it) } - ?: throw JsonParseException("Can't decode base64 ByteArray: $raw") - } - } - } - - class RetroFitConverterFactory @Inject constructor() : Converter.Factory() { - - override fun requestBodyConverter( - type: Type, - parameterAnnotations: Array<out Annotation>, - methodAnnotations: Array<out Annotation>, - retrofit: Retrofit - ): Converter<RawCOSEObject, RequestBody> { - return ConverterToBody() - } - - override fun responseBodyConverter( - type: Type, - annotations: Array<out Annotation>, - retrofit: Retrofit - ): Converter<ResponseBody, RawCOSEObject> { - return ConverterFromBody() - } - - class ConverterFromBody : Converter<ResponseBody, RawCOSEObject> { - override fun convert(value: ResponseBody): RawCOSEObject { - val rawData = value.byteString() - return RawCOSEObject(rawData) - } - } - - class ConverterToBody : Converter<RawCOSEObject, RequestBody> { - override fun convert(value: RawCOSEObject): RequestBody { - return value.data.toRequestBody( - "application/octet-stream".toMediaTypeOrNull() - ) - } - } - } -} +typealias RawCOSEObject = ByteArray diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/VaccinationDGCV1Parser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/VaccinationDGCV1Parser.kt index c2d664c19e97452d97cc9ca4f5c2e80c4f9fdb16..e25defbc9cab81c1be8c37c0f6aba41716b14f75 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/VaccinationDGCV1Parser.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/certificate/VaccinationDGCV1Parser.kt @@ -35,9 +35,9 @@ class VaccinationDGCV1Parser @Inject constructor( if (vaccinationDatas.isEmpty()) { throw InvalidHealthCertificateException(VC_NO_VACCINATION_ENTRY) } + // Force date parsing dateOfBirth vaccinationDatas.forEach { - // Force date parsing it.vaccinatedAt } return this diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/common/RawCOSEObject.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/common/RawCOSEObject.kt deleted file mode 100644 index 170af7e10caadfd5dc1149ca9472cd5b85a4463d..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/common/RawCOSEObject.kt +++ /dev/null @@ -1,39 +0,0 @@ -package de.rki.coronawarnapp.vaccination.core.common - -import com.google.gson.JsonParseException -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter -import okio.ByteString -import okio.ByteString.Companion.decodeBase64 -import okio.ByteString.Companion.toByteString -import org.json.JSONObject - -data class RawCOSEObject( - val data: ByteString -) { - constructor(data: ByteArray) : this(data.toByteString()) - - val asByteArray: ByteArray - get() = data.toByteArray() - - companion object { - val EMPTY = RawCOSEObject(data = ByteString.EMPTY) - } - - class JsonAdapter : TypeAdapter<RawCOSEObject>() { - override fun write(out: JsonWriter, value: RawCOSEObject?) { - if (value == null) out.nullValue() - else value.data.base64().let { out.value(it) } - } - - override fun read(reader: JsonReader): RawCOSEObject? = when (reader.peek()) { - JSONObject.NULL -> reader.nextNull().let { null } - else -> { - val raw = reader.nextString() - raw.decodeBase64()?.let { RawCOSEObject(data = it) } - ?: throw JsonParseException("Can't decode base64 ByteArray: $raw") - } - } - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateCOSEParser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateCOSEParser.kt deleted file mode 100644 index 45bdaf3263040a11abdf843ffb590127c437be46..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateCOSEParser.kt +++ /dev/null @@ -1,27 +0,0 @@ -package de.rki.coronawarnapp.vaccination.core.qrcode - -import de.rki.coronawarnapp.vaccination.core.certificate.HealthCertificateCOSEDecoder -import de.rki.coronawarnapp.vaccination.core.certificate.HealthCertificateHeaderParser -import de.rki.coronawarnapp.vaccination.core.certificate.RawCOSEObject -import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1Parser -import timber.log.Timber -import javax.inject.Inject - -class VaccinationCertificateCOSEParser @Inject constructor( - private val coseDecoder: HealthCertificateCOSEDecoder, - private val headerParser: HealthCertificateHeaderParser, - private val bodyParser: VaccinationDGCV1Parser, -) { - - fun parse(rawCOSEObject: RawCOSEObject): VaccinationCertificateData { - Timber.v("Parsing COSE for vaccination certificate.") - val cbor = coseDecoder.decode(rawCOSEObject) - - return VaccinationCertificateData( - header = headerParser.parse(cbor), - certificate = bodyParser.parse(cbor) - ).also { - Timber.v("Parsed vaccination certificate for %s", it.certificate.nameData.familyNameStandardized) - } - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateData.kt index b0661dcc7a824926c4c9aa971f66ca66879b32ac..31da1c88bc2ad193a46614debcbcaea02feae7a6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateData.kt @@ -4,7 +4,7 @@ import de.rki.coronawarnapp.vaccination.core.certificate.CoseCertificateHeader import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1 /** - * Represents the information gained from data in COSE representation + * Represents the parsed data from the QR code */ data class VaccinationCertificateData( val header: CoseCertificateHeader, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateQRCode.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateQRCode.kt index 994fd52a3c25d7b32bd169c9c02ecf5769a9dc47..5d72448a8a526c2954b15ffb67fa244fb20768b5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateQRCode.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationCertificateQRCode.kt @@ -1,11 +1,11 @@ package de.rki.coronawarnapp.vaccination.core.qrcode -import de.rki.coronawarnapp.vaccination.core.certificate.RawCOSEObject - data class VaccinationCertificateQRCode( + val qrCodeString: QrCodeString, val parsedData: VaccinationCertificateData, - val certificateCOSE: RawCOSEObject, ) { val uniqueCertificateIdentifier: String get() = parsedData.certificate.vaccinationDatas.single().uniqueCertificateIdentifier } + +typealias QrCodeString = String diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractor.kt index bf604bbf7601819b97cfdb92f7b84218250621ce..b4ab3e75377eb1e08946d52e27f01f3175155e9e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/qrcode/VaccinationQRCodeExtractor.kt @@ -2,51 +2,68 @@ package de.rki.coronawarnapp.vaccination.core.qrcode import de.rki.coronawarnapp.coronatest.qrcode.QrCodeExtractor import de.rki.coronawarnapp.util.compression.inflate -import de.rki.coronawarnapp.util.encoding.decodeBase45 +import de.rki.coronawarnapp.util.encoding.Base45Decoder +import de.rki.coronawarnapp.vaccination.core.certificate.HealthCertificateCOSEDecoder +import de.rki.coronawarnapp.vaccination.core.certificate.HealthCertificateHeaderParser import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException.ErrorCode.HC_BASE45_DECODING_FAILED import de.rki.coronawarnapp.vaccination.core.certificate.InvalidHealthCertificateException.ErrorCode.HC_ZLIB_DECOMPRESSION_FAILED import de.rki.coronawarnapp.vaccination.core.certificate.RawCOSEObject -import okio.ByteString +import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1Parser import timber.log.Timber import javax.inject.Inject class VaccinationQRCodeExtractor @Inject constructor( - private val vaccinationCertificateCOSEParser: VaccinationCertificateCOSEParser, + private val coseDecoder: HealthCertificateCOSEDecoder, + private val headerParser: HealthCertificateHeaderParser, + private val bodyParser: VaccinationDGCV1Parser, ) : QrCodeExtractor<VaccinationCertificateQRCode> { override fun canHandle(rawString: String): Boolean = rawString.startsWith(PREFIX) override fun extract(rawString: String): VaccinationCertificateQRCode { - val rawCOSEObject = rawString + val parsedData = rawString .removePrefix(PREFIX) - .tryDecodeBase45() + .decodeBase45() .decompress() + .parse() return VaccinationCertificateQRCode( - parsedData = vaccinationCertificateCOSEParser.parse(rawCOSEObject), - certificateCOSE = rawCOSEObject, + parsedData = parsedData, + qrCodeString = rawString, ) } - private fun String.tryDecodeBase45(): ByteString = try { - this.decodeBase45() + private fun String.decodeBase45(): ByteArray = try { + Base45Decoder.decode(this) } catch (e: Throwable) { Timber.e(e) throw InvalidHealthCertificateException(HC_BASE45_DECODING_FAILED) } - private fun ByteString.decompress(): RawCOSEObject = try { - RawCOSEObject(this.inflate(sizeLimit = DEFAULT_SIZE_LIMIT)) + private fun ByteArray.decompress(): RawCOSEObject = try { + this.inflate(sizeLimit = DEFAULT_SIZE_LIMIT) } catch (e: Throwable) { Timber.e(e) throw InvalidHealthCertificateException(HC_ZLIB_DECOMPRESSION_FAILED) } + fun RawCOSEObject.parse(): VaccinationCertificateData { + Timber.v("Parsing COSE for vaccination certificate.") + val cbor = coseDecoder.decode(this) + + return VaccinationCertificateData( + header = headerParser.parse(cbor), + certificate = bodyParser.parse(cbor) + ).also { + Timber.v("Parsed vaccination certificate for %s", it.certificate.nameData.familyNameStandardized) + } + } + companion object { private const val PREFIX = "HC1:" // Zip bomb - const val DEFAULT_SIZE_LIMIT = 1024L * 1024 * 10L // 10 MB + private const val DEFAULT_SIZE_LIMIT = 1024L * 1024 * 10L // 10 MB } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt index ca2b7dc7eee993fbf82ce56d147aa0265bd833fd..1621f9eea96f84d4e1af2dcda9450efd73ab6fcc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/VaccinationRepository.kt @@ -10,8 +10,8 @@ import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson import de.rki.coronawarnapp.vaccination.core.VaccinatedPersonIdentifier import de.rki.coronawarnapp.vaccination.core.VaccinationCertificate import de.rki.coronawarnapp.vaccination.core.personIdentifier -import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateCOSEParser import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateQRCode +import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor import de.rki.coronawarnapp.vaccination.core.repository.errors.VaccinationCertificateNotFoundException import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinatedPersonData import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinationContainer @@ -36,8 +36,8 @@ class VaccinationRepository @Inject constructor( dispatcherProvider: DispatcherProvider, private val timeStamper: TimeStamper, private val storage: VaccinationStorage, - private val valueSetsRepository: ValueSetsRepository, - private val vaccionationCoseParser: VaccinationCertificateCOSEParser, + valueSetsRepository: ValueSetsRepository, + private val vaccinationQRCodeExtractor: VaccinationQRCodeExtractor, ) { private val internalData: HotDataFlow<Set<VaccinatedPerson>> = HotDataFlow( @@ -102,7 +102,7 @@ class VaccinationRepository @Inject constructor( val newCertificate = qrCode.toVaccinationContainer( scannedAt = timeStamper.nowUTC, - coseParser = vaccionationCoseParser, + qrCodeExtractor = vaccinationQRCodeExtractor, ) val modifiedPerson = originalPerson.copy( 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 636530163dea7d8e048f678347bdef61b2a100d8..c4f2debb082773d1baefb0f9483151182d42e0b5 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,14 +7,14 @@ 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.vaccination.core.qrcode.VaccinationCertificateCOSEParser +import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor import timber.log.Timber import java.io.IOException import javax.inject.Inject @Reusable class ContainerPostProcessor @Inject constructor( - private val vaccinationCertificateCOSEParser: VaccinationCertificateCOSEParser, + private val qrCodeExtractor: VaccinationQRCodeExtractor, ) : TypeAdapterFactory { override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> { val delegate = gson.getDelegateAdapter(this, type) @@ -30,7 +30,7 @@ class ContainerPostProcessor @Inject constructor( when (obj) { is VaccinationContainer -> { Timber.v("Injecting VaccinationContainer %s", obj.hashCode()) - obj.parser = vaccinationCertificateCOSEParser + obj.qrCodeExtractor = qrCodeExtractor } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt index 01dd4aaf9908afeaa2d04a6a021787d3f5b056fd..76ca4e4b5c2840800c9f0c0c19be965abad73ba9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt @@ -6,33 +6,33 @@ import de.rki.coronawarnapp.ui.Country import de.rki.coronawarnapp.vaccination.core.VaccinatedPersonIdentifier import de.rki.coronawarnapp.vaccination.core.VaccinationCertificate import de.rki.coronawarnapp.vaccination.core.certificate.CoseCertificateHeader -import de.rki.coronawarnapp.vaccination.core.certificate.RawCOSEObject import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1 import de.rki.coronawarnapp.vaccination.core.personIdentifier -import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateCOSEParser +import de.rki.coronawarnapp.vaccination.core.qrcode.QrCodeString import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateQRCode +import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet import org.joda.time.Instant import org.joda.time.LocalDate @Keep data class VaccinationContainer internal constructor( - @SerializedName("vaccinationCertificateCOSE") val vaccinationCertificateCOSE: RawCOSEObject, + @SerializedName("vaccinationQrCode") val vaccinationQrCode: QrCodeString, @SerializedName("scannedAt") val scannedAt: Instant, ) { // Either set by [ContainerPostProcessor] or via [toVaccinationContainer] - @Transient lateinit var parser: VaccinationCertificateCOSEParser + @Transient lateinit var qrCodeExtractor: VaccinationQRCodeExtractor @Transient internal var preParsedData: VaccinationCertificateData? = null // Otherwise GSON unsafes reflection to create this class, and sets the LAZY to null @Suppress("unused") - constructor() : this(RawCOSEObject.EMPTY, Instant.EPOCH) + constructor() : this("", Instant.EPOCH) @delegate:Transient private val certificateData: VaccinationCertificateData by lazy { - preParsedData ?: parser.parse(vaccinationCertificateCOSE) + preParsedData ?: qrCodeExtractor.extract(vaccinationQrCode).parsedData } val header: CoseCertificateHeader @@ -91,16 +91,19 @@ data class VaccinationContainer internal constructor( get() = header.issuedAt override val expiresAt: Instant get() = header.expiresAt + + override val vaccinationQrCodeString: QrCodeString + get() = vaccinationQrCode } } fun VaccinationCertificateQRCode.toVaccinationContainer( scannedAt: Instant, - coseParser: VaccinationCertificateCOSEParser, + qrCodeExtractor: VaccinationQRCodeExtractor, ) = VaccinationContainer( - vaccinationCertificateCOSE = certificateCOSE, + vaccinationQrCode = this.qrCodeString, scannedAt = scannedAt, ).apply { - parser = coseParser + this.qrCodeExtractor = qrCodeExtractor preParsedData = parsedData } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorage.kt index 05366335217111de618e42c3c56ab9401642622b..ee8d04c714deaf19c73b3d36dde07c43b3c7bd92 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorage.kt @@ -6,7 +6,6 @@ import com.google.gson.Gson import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.serialization.BaseGson import de.rki.coronawarnapp.util.serialization.fromJson -import de.rki.coronawarnapp.vaccination.core.certificate.RawCOSEObject import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -25,7 +24,6 @@ class VaccinationStorage @Inject constructor( private val gson by lazy { // Allow for custom type adapter. baseGson.newBuilder().apply { - registerTypeAdapter(RawCOSEObject::class.java, RawCOSEObject.JsonAdapter()) registerTypeAdapterFactory(containerPostProcessor) }.create() } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/compression/ZLIBCompressionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/compression/ZLIBCompressionTest.kt index f924acfd412b7ba3b7329c771d7125efbaceb50c..4233793344df911390624521889f7bdb0a32e120 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/compression/ZLIBCompressionTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/compression/ZLIBCompressionTest.kt @@ -12,13 +12,13 @@ class ZLIBCompressionTest : BaseTest() { @Test fun `basic decompression`() { - ZLIBCompression().decompress(compressed).utf8() shouldBe "The Cake Is A Lie" + ZLIBCompression().decompress(compressed.toByteArray()) shouldBe "The Cake Is A Lie".toByteArray() } @Test fun `invalid decompression`() { val error = shouldThrow<InvalidInputException> { - ZLIBCompression().decompress(compressed.substring(5)) + ZLIBCompression().decompress(compressed.substring(5).toByteArray()) } error.causes().first { it is DataFormatException }.message shouldBe "incorrect header check" } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encoding/Base45ExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encoding/Base45ExtensionsTest.kt index f3af93059e26963f129702b15665b9fb086deb52..b0256e9a4b3ec7bc9e4b67d76857a22788c8984c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encoding/Base45ExtensionsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encoding/Base45ExtensionsTest.kt @@ -1,7 +1,6 @@ package de.rki.coronawarnapp.util.encoding import io.kotest.matchers.shouldBe -import okio.ByteString.Companion.toByteString import okio.internal.commonAsUtf8ToByteArray import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -10,15 +9,15 @@ class Base45ExtensionsTest : BaseTest() { @Test fun `encode - extension`() { - "AB".toByteArray().toByteString().base45() shouldBe "BB8" - "Hello!!".commonAsUtf8ToByteArray().toByteString().base45() shouldBe "%69 VD92EX0" - "base-45".commonAsUtf8ToByteArray().toByteString().base45() shouldBe "UJCLQE7W581" + "AB".toByteArray().base45() shouldBe "BB8" + "Hello!!".commonAsUtf8ToByteArray().base45() shouldBe "%69 VD92EX0" + "base-45".commonAsUtf8ToByteArray().base45() shouldBe "UJCLQE7W581" } @Test fun `decode - extension`() { - "BB8".decodeBase45() shouldBe "AB".toByteArray().toByteString() - "%69 VD92EX0".decodeBase45() shouldBe "Hello!!".toByteArray().toByteString() - "UJCLQE7W581".decodeBase45() shouldBe "base-45".toByteArray().toByteString() + "BB8".decodeBase45() shouldBe "AB".toByteArray() + "%69 VD92EX0".decodeBase45() shouldBe "Hello!!".toByteArray() + "UJCLQE7W581".decodeBase45() shouldBe "base-45".toByteArray() } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/RawCOSEObjectTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/RawCOSEObjectTest.kt deleted file mode 100644 index b1759ba4967e7e89bc365bab262062a8f722eeb5..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/RawCOSEObjectTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package de.rki.coronawarnapp.vaccination.core - -import com.google.gson.GsonBuilder -import de.rki.coronawarnapp.util.serialization.fromJson -import de.rki.coronawarnapp.vaccination.core.certificate.RawCOSEObject -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import testhelpers.BaseTest - -class RawCOSEObjectTest : BaseTest() { - - @BeforeEach - fun setup() { - } - - @Test - fun `comparison and conversion`() { - val rawRaw = "The Cake Is A Lie!".toByteArray() - val rawRaw2 = "The Cake Is Not A Lie!".toByteArray() // This is a lie - val rawCOSEObject1 = RawCOSEObject(rawRaw) - val rawCOSEObject2 = RawCOSEObject(rawRaw2) - - rawRaw shouldNotBe rawRaw2 - rawCOSEObject1 shouldNotBe rawCOSEObject2 - - rawCOSEObject1.asByteArray shouldBe rawRaw - rawCOSEObject2.asByteArray shouldBe rawRaw2 - } - - @Test - fun `serialization and deserialization`() { - val rawRaw = "The Cake Is A Lie!".toByteArray() - val rawRaw2 = "The Cake Is Not A Lie!".toByteArray() // This is a lie - val rawCOSEObject1 = RawCOSEObject(rawRaw) - val rawCOSEObject2 = RawCOSEObject(rawRaw2) - - val gson = GsonBuilder().apply { - registerTypeAdapter(RawCOSEObject::class.java, RawCOSEObject.JsonAdapter()) - }.create() - - val json1 = gson.toJson(rawCOSEObject1) - json1 shouldBe "\"VGhlIENha2UgSXMgQSBMaWUh\"" - gson.fromJson<RawCOSEObject>(json1) shouldBe rawCOSEObject1 - - val json2 = gson.toJson(rawCOSEObject2) - json2 shouldBe "\"VGhlIENha2UgSXMgTm90IEEgTGllIQ\\u003d\\u003d\"" - gson.fromJson<RawCOSEObject>(json2) shouldBe rawCOSEObject2 - } -} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt index a998406bb63c4697f1ae44255329d1e4798a6c6d..e5c19e3297000f5c72f776717d2ada42571fa159 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/VaccinationTestData.kt @@ -1,28 +1,20 @@ package de.rki.coronawarnapp.vaccination.core -import de.rki.coronawarnapp.util.compression.inflate -import de.rki.coronawarnapp.util.encoding.decodeBase45 -import de.rki.coronawarnapp.vaccination.core.certificate.RawCOSEObject import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1 -import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateCOSEParser +import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinatedPersonData import de.rki.coronawarnapp.vaccination.core.repository.storage.VaccinationContainer import org.joda.time.Instant import javax.inject.Inject class VaccinationTestData @Inject constructor( - private val vaccinationCertificateCOSEParser: VaccinationCertificateCOSEParser, + private var qrCodeExtractor: VaccinationQRCodeExtractor, ) { // AndreasAstra1.pdf val personAVac1QR = "HC1:6BFOXN*TS0BI\$ZD.P9UOL97O4-2HH77HRM3DSPTLRR+%3KXH9M9ESIGUBA KWML%6S5B9-+P70Q5VC9:BPCNYKMXEE1JAA/CXGG0JK1WL260X638J3-E3ND3DAJ-43TTTO3HK1H3QBCWNZ83UQJ:T0/8F7V0HKN:Q8.HBV+0SZ4GH00T9UKP0T9WC5PF6846A\$Q$76QW6%V98T5\$FQMI5DN9QZ5Y0Q\$UPE%5MZ5*T57ZA\$O7T6LEJOA+MZ55EII-EB1EKC422JBBD0D2K.EJJ14B2MP41WTRZPQEC5L64HX6IAS 8S8FT/MAMXP6QS03L0QIRR97I2HOAXL92L0. KOKG8VG5SI:TU+MMPZ55%PBT1YEGEA7IB65C94JBQ2NLEE:NQ% GC3MXHFLF9OIFN0IZ95LJL80P1FDLW452I8941:HH3M41GTNP8EFUNT$.FTD852IWKP/HLIJL8JF8JF172IMAS EDAHMXFBFBQSKJE72KV\$FHJ%3O%6:XM+1QD+T2/VKKER3L3%1THL7MGY.1S:T:GLOX6OCE7+RWYL3.C-L27WNV0G::M74O%K7C50AAEI4" - val personAVac1COSE: RawCOSEObject = personAVac1QR - .removePrefix("HC1:") - .decodeBase45().inflate() - .let { RawCOSEObject(data = it) } - val personAVac1Certificate = VaccinationDGCV1( version = "1.0.0", nameData = VaccinationDGCV1.NameData( @@ -50,20 +42,15 @@ class VaccinationTestData @Inject constructor( val personAVac1Container = VaccinationContainer( scannedAt = Instant.ofEpochMilli(1620062834471), - vaccinationCertificateCOSE = personAVac1COSE, + vaccinationQrCode = personAVac1QR, ).apply { - parser = vaccinationCertificateCOSEParser + qrCodeExtractor = this@VaccinationTestData.qrCodeExtractor } // AndreasAstra2.pdf val personAVac2QR = "6BFOXN*TS0BI\$ZD.P9UOL97O4-2HH77HRM3DSPTLRR+%3D H9M9ESIGUBA KWMLYX1HXK 0DV:D5VC9:BPCNYKMXEE1JAA/CZIK0JK1WL260X638J3-E3ND3DAJ-43TTTMDF6S8:B73QN VNZ.0K6HYI3CNN96BPHNW*0I85V.499TXY9KK9%OC+G9QJPNF67J6QW67KQ9G66PPM4MLJE+.PDB9L6Q2+PFQ5DB96PP5/P-59A%N+892 7J235II3NJ7PK7SLQMIPUBN9CIZI.EJJ14B2MP41IZRZPQEC5L64HX6IAS 8SAFT/MAMXP6QS03L0QIRR97I2HOAXL92L0. KOKGGVG5SI:TU+MMPZ55%PBT1YEGEA7IB65C94JBQ2NLEE:NQ% GC3MXHFLF9OIFN0IZ95LJL80P1FDLW452I8941:HH3M41GTNP8EFUNT\$.FTD852IWKP/HLIJL8JF8JF172E2JA0K*WDQMPB8T3%KLUSR43M.F\$QBQDR\$VT7V01Y7J0BOZLH+D-QF6MO\$R3%XB+.4QI596GY\$SITJP5BS0DFROC.7B.2RTB*UNYSM$*00HIL+H" - val personAVac2COSE: RawCOSEObject = personAVac2QR - .removePrefix("HC1:") - .decodeBase45().inflate() - .let { RawCOSEObject(data = it) } - val personAVac2Certificate = VaccinationDGCV1( version = "1.0.0", nameData = VaccinationDGCV1.NameData( @@ -91,9 +78,9 @@ class VaccinationTestData @Inject constructor( val personAVac2Container = VaccinationContainer( scannedAt = Instant.ofEpochMilli(1620069934471), - vaccinationCertificateCOSE = personAVac2COSE, + vaccinationQrCode = personAVac2QR, ).apply { - parser = vaccinationCertificateCOSEParser + qrCodeExtractor = this@VaccinationTestData.qrCodeExtractor } val personAData2Vac1Proof = VaccinatedPersonData( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt index f931969cdc7a2f15da06aedf736642d47e00c507..a888628a60455a202632c24e041d74366ce81758 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationStorageTest.kt @@ -70,10 +70,10 @@ class VaccinationStorageTest : BaseTest() { { "vaccinationData": [ { - "vaccinationCertificateCOSE": "${testData.personAVac1COSE.data.base64()}", + "vaccinationQrCode": "${testData.personAVac1QR}", "scannedAt": 1620062834471 }, { - "vaccinationCertificateCOSE": "${testData.personAVac2COSE.data.base64()}", + "vaccinationQrCode": "${testData.personAVac2QR}", "scannedAt": 1620069934471 } ]