diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index e019130e234c5583f3c222b03c296f93e0a3c42f..948ff9a9f433ca51ceebe8bfe1726be49a643aec 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -441,6 +441,4 @@ dependencies { // HCert implementation("com.upokecenter:cbor:4.4.1") - - implementation("bouncycastle:bcprov-jdk16:136") } 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 8f3c1b148093f66d685c9d294dc116a0c6fd898a..8656d45a6b112e8d2846d02d38e7ea926bad2495 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 @@ -8,6 +8,11 @@ import de.rki.coronawarnapp.coronatest.type.TestCertificateContainer import de.rki.coronawarnapp.coronatest.type.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.covidcertificate.test.TestCertificateQRCodeExtractor @@ -247,7 +252,11 @@ class TestCertificateRepository @Inject constructor( return cert } - val rsaKeyPair = rsaKeyPairGenerator.generate() + val rsaKeyPair = try { + rsaKeyPairGenerator.generate() + } catch (e: Throwable) { + throw InvalidTestCertificateException(RSA_KP_GENERATION_FAILED) + } withContext(dispatcherProvider.IO) { certificateServer.registerPublicKeyForTest( @@ -315,21 +324,29 @@ class TestCertificateRepository @Inject constructor( try { executeRequest() - } catch (e: Exception) { - // TODO catch a specific error that reflects error code DGC_COMP_202 - delay(certConfig.waitForRetry.millis) - 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 = rsaCryptography.decrypt( - toDecrypt = components.dataEncryptionKeyBase64.decodeBase64()!!, - privateKey = cert.rsaPrivateKey!! - ) + 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(), + decryptionKey = encryptionKey.toByteArray(), rawCoseObjectEncrypted = components.encryptedCoseTestCertificateBase64.decodeBase64()!!.toByteArray() ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptography.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptography.kt index 261fc2a020db85d8e5e56851dfdbbca88acc2234..ac294903083ca20e2b70d57069d63223fc9d7c18 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptography.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptography.kt @@ -1,36 +1,24 @@ package de.rki.coronawarnapp.covidcertificate.cryptography import com.google.android.gms.common.util.Hex -import dagger.Reusable -import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.bouncycastle.util.encoders.Base64.decode -import java.security.Security import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec import javax.inject.Inject -@Reusable class AesCryptography @Inject constructor() { - private val ivParameterSpec - get() = IvParameterSpec(Hex.stringToBytes("00000000000000000000000000000000")) - fun decrypt( decryptionKey: ByteArray, encryptedData: ByteArray - ): ByteArray { - Security.addProvider(BouncyCastleProvider()) - val keySpec = SecretKeySpec(decode(decryptionKey), ALGORITHM) - val input = decode(encryptedData) - return with(Cipher.getInstance(TRANSFORMATION)) { - init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec) - val output = ByteArray(getOutputSize(input.size)) - var outputLength = update(input, 0, input.size, output, 0) - outputLength += doFinal(output, outputLength) - output.copyOfRange(0, outputLength) - } + ): ByteArray = with(Cipher.getInstance(TRANSFORMATION)) { + val keySpec = SecretKeySpec(decryptionKey, ALGORITHM) + init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec) + doFinal(encryptedData) } + + private val ivParameterSpec + get() = IvParameterSpec(Hex.stringToBytes("00000000000000000000000000000000")) } private const val ALGORITHM = "AES" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/ErrorMessage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/ErrorMessage.kt new file mode 100644 index 0000000000000000000000000000000000000000..9c96623b96597c703525b5bcea1542c234d6a8af --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/ErrorMessage.kt @@ -0,0 +1,14 @@ +package de.rki.coronawarnapp.covidcertificate.exception + +import de.rki.coronawarnapp.R + +const val ERROR_MESSAGE_GENERIC = R.string.errors_generic_text_unknown_error_cause + +// TODO change to correct error message once provided +const val ERROR_MESSAGE_TRY_AGAIN = ERROR_MESSAGE_GENERIC +const val ERROR_MESSAGE_DCC_NOT_SUPPORTED_BY_LAB = ERROR_MESSAGE_GENERIC +const val ERROR_MESSAGE_NO_NETWORK = ERROR_MESSAGE_GENERIC +const val ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE = ERROR_MESSAGE_GENERIC +const val ERROR_MESSAGE_TRY_AGAIN_DCC_NOT_AVAILABLE_YET = ERROR_MESSAGE_GENERIC +const val ERROR_MESSAGE_CLIENT_ERROR_CALL_HOTLINE = ERROR_MESSAGE_GENERIC +const val ERROR_MESSAGE_DCC_EXPIRED = ERROR_MESSAGE_GENERIC diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidHealthCertificateException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidHealthCertificateException.kt index 16b90b3db903c63889f85a15146735b99dfdea4f..fe31b111213737dbfc4e4fa7645e8950bf0b28ef 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidHealthCertificateException.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidHealthCertificateException.kt @@ -1,7 +1,6 @@ package de.rki.coronawarnapp.covidcertificate.exception import android.content.Context -import de.rki.coronawarnapp.R import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException import de.rki.coronawarnapp.util.HasHumanReadableError import de.rki.coronawarnapp.util.HumanReadableError @@ -34,26 +33,8 @@ open class InvalidHealthCertificateException( HC_CWT_NO_EXP("Expiration date missing."), HC_CWT_NO_HCERT("Health certificate missing."), HC_CWT_NO_ISS("Issuer missing."), + AES_DECRYPTION_FAILED("AES decryption failed"), - DCC_COMP_202("DCC Test Certificate Components failed with error 202: DCC pending."), - DCC_COMP_400("DCC Test Certificate Components failed with error 400: Bad request (e.g. wrong format of registration token)"), - DCC_COMP_404("DCC Test Certificate Components failed with error 404: Registration token does not exist."), - DCC_COMP_410("DCC Test Certificate Components failed with error 410: DCC already cleaned up."), - DCC_COMP_412("DCC Test Certificate Components failed with error 412: Test result not yet received"), - DCC_COMP_500_INTERNAL("DCC Test Certificate Components failed with error 500: Internal server error."), - DCC_COMP_500_LAB_INVALID_RESPONSE("DCC Test Certificate Components failed with error 500: Lab Invalid response"), - DCC_COMP_500_SIGNING_CLIENT_ERROR("DCC Test Certificate Components failed with error 500: Signing client error"), - DCC_COMP_500_SIGNING_SERVER_ERROR("DCC Test Certificate Components failed with error 500: Signing server error"), - DCC_COMP_NO_NETWORK("DCC Test Certificate Components failed due to no network connection."), - DCC_COSE_MESSAGE_INVALID("COSE message invalid."), - DCC_COSE_TAG_INVALID("COSE tag invalid."), - PKR_400("Public Key Registration failed with error 400: Bad request (e.g. wrong format of registration token or public key)."), - PKR_403("Public Key Registration failed with error 403: Registration token is not allowed to issue a DCC."), - PKR_404("Public Key Registration failed with error 404: Registration token does not exist."), - PKR_409("Public Key Registration failed with error 409: Registration token is already assigned to a public key."), - PKR_500("Public Key Registration failed with error 500: Internal server error."), - PKR_FAILED("Private key request failed."), - PKR_NO_NETWORK("Private key request failed due to no network connection."), RSA_DECRYPTION_FAILED("RSA decryption failed."), RSA_KP_GENERATION_FAILED("RSA key pair generation failed."), } @@ -65,9 +46,7 @@ open class InvalidHealthCertificateException( override fun toHumanReadableError(context: Context): HumanReadableError { return HumanReadableError( - description = errorMessage.get(context) + description = errorMessage.get(context) + "/n/n$errorCode" ) } } - -private const val ERROR_MESSAGE_GENERIC = R.string.errors_generic_text_unknown_error_cause diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidTestCertificateException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidTestCertificateException.kt index ca8909e4605f99dc8bfbf1cc785ba622c60ad2dc..360301b1c893587a41a7087834bce0335e694cb6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidTestCertificateException.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/InvalidTestCertificateException.kt @@ -1,92 +1,31 @@ package de.rki.coronawarnapp.covidcertificate.exception import android.content.Context -import de.rki.coronawarnapp.R import de.rki.coronawarnapp.util.HumanReadableError import de.rki.coronawarnapp.util.ui.CachedString import de.rki.coronawarnapp.util.ui.LazyString class InvalidTestCertificateException(errorCode: ErrorCode) : InvalidHealthCertificateException(errorCode) { override fun toHumanReadableError(context: Context): HumanReadableError { - var errorCodeString = errorCode.toString() - errorCodeString = if (errorCodeString.startsWith(PREFIX_TC)) errorCodeString else PREFIX_TC + errorCodeString return HumanReadableError( - description = errorMessage.get(context) + "\n\n$errorCodeString" + description = errorMessage.get(context) + "\n\n$errorCode" ) } override val errorMessage: LazyString get() = when (errorCode) { - ErrorCode.DCC_COMP_NO_NETWORK, - ErrorCode.PKR_NO_NETWORK -> CachedString { context -> - context.getString(ERROR_MESSAGE_NO_NETWORK) - } - - ErrorCode.DCC_COMP_202 -> CachedString { context -> - context.getString(ERROR_MESSAGE_TRY_AGAIN_DCC_NOT_AVAILABLE_YET) - } - - ErrorCode.DCC_COMP_410 -> CachedString { context -> - context.getString(ERROR_MESSAGE_DCC_EXPIRED) - } - - // TODO -/* ErrorCode.HC_BASE45_DECODING_FAILED, - ErrorCode.HC_CBOR_DECODING_FAILED, - ErrorCode.HC_COSE_MESSAGE_INVALID, - ErrorCode.HC_ZLIB_DECOMPRESSION_FAILED, - ErrorCode.HC_COSE_TAG_INVALID, - ErrorCode.HC_CWT_NO_DGC, - ErrorCode.HC_CWT_NO_EXP, - ErrorCode.HC_CWT_NO_HCERT, - ErrorCode.HC_CWT_NO_ISS, - ErrorCode.JSON_SCHEMA_INVALID,*/ - ErrorCode.AES_DECRYPTION_FAILED, ErrorCode.RSA_DECRYPTION_FAILED, - ErrorCode.DCC_COSE_MESSAGE_INVALID, - ErrorCode.DCC_COSE_TAG_INVALID, - ErrorCode.DCC_COMP_404, - ErrorCode.DCC_COMP_412, - ErrorCode.PKR_403, - ErrorCode.PKR_404 -> CachedString { context -> + ErrorCode.HC_COSE_MESSAGE_INVALID, + ErrorCode.HC_COSE_TAG_INVALID -> CachedString { context -> context.getString(ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE) } - ErrorCode.DCC_COMP_400, - ErrorCode.PKR_400 -> CachedString { context -> - context.getString(ERROR_MESSAGE_CLIENT_ERROR_CALL_HOTLINE) - } - - ErrorCode.PKR_FAILED, - ErrorCode.RSA_KP_GENERATION_FAILED, - ErrorCode.PKR_500, - ErrorCode.DCC_COMP_500_INTERNAL -> CachedString { context -> + ErrorCode.RSA_KP_GENERATION_FAILED -> CachedString { context -> context.getString(ERROR_MESSAGE_TRY_AGAIN) } - ErrorCode.HC_BASE45_ENCODING_FAILED, - ErrorCode.HC_ZLIB_COMPRESSION_FAILED, - ErrorCode.NO_TEST_ENTRY, - ErrorCode.DCC_COMP_500_LAB_INVALID_RESPONSE, - ErrorCode.DCC_COMP_500_SIGNING_CLIENT_ERROR, - ErrorCode.DCC_COMP_500_SIGNING_SERVER_ERROR, - ErrorCode.PKR_409 -> CachedString { context -> - context.getString(ERROR_MESSAGE_GENERIC) - } else -> super.errorMessage } } - -private const val PREFIX_TC = "TC_" -private const val ERROR_MESSAGE_GENERIC = R.string.errors_generic_text_unknown_error_cause - -// TODO change to correct error message once provided -private const val ERROR_MESSAGE_TRY_AGAIN = ERROR_MESSAGE_GENERIC -private const val ERROR_MESSAGE_DCC_NOT_SUPPORTED_BY_LAB = ERROR_MESSAGE_GENERIC -private const val ERROR_MESSAGE_NO_NETWORK = ERROR_MESSAGE_GENERIC -private const val ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE = ERROR_MESSAGE_GENERIC -private const val ERROR_MESSAGE_TRY_AGAIN_DCC_NOT_AVAILABLE_YET = ERROR_MESSAGE_GENERIC -private const val ERROR_MESSAGE_CLIENT_ERROR_CALL_HOTLINE = ERROR_MESSAGE_GENERIC -private const val ERROR_MESSAGE_DCC_EXPIRED = ERROR_MESSAGE_GENERIC diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/TestCertificateServerException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/TestCertificateServerException.kt new file mode 100644 index 0000000000000000000000000000000000000000..0935900237be8945628a49e506bd0a8bc7218ecb --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/exception/TestCertificateServerException.kt @@ -0,0 +1,102 @@ +package de.rki.coronawarnapp.covidcertificate.exception + +import android.content.Context +import androidx.annotation.StringRes +import de.rki.coronawarnapp.util.HasHumanReadableError +import de.rki.coronawarnapp.util.HumanReadableError +import de.rki.coronawarnapp.util.ui.CachedString +import de.rki.coronawarnapp.util.ui.LazyString + +class TestCertificateServerException( + val errorCode: ErrorCode +) : HasHumanReadableError, Throwable(errorCode.message) { + + override fun toHumanReadableError(context: Context): HumanReadableError { + return HumanReadableError( + description = errorMessage.get(context) + ) + } + + val errorMessage: LazyString + get() = CachedString { context -> + context.getString(errorCode.stringRes) + } + + enum class ErrorCode( + val message: String, + @StringRes val stringRes: Int + ) { + DCC_COMP_202( + "DCC Components request failed with error 202: DCC pending.", + ERROR_MESSAGE_TRY_AGAIN_DCC_NOT_AVAILABLE_YET + ), + DCC_COMP_400( + "DCC Components request failed with error 400: Bad request (e.g. wrong format of registration token)", + ERROR_MESSAGE_CLIENT_ERROR_CALL_HOTLINE + ), + DCC_COMP_404( + "DCC Components request failed with error 404: Registration token does not exist.", + ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE + ), + DCC_COMP_410( + "DCC Components request failed with error 410: DCC already cleaned up.", + ERROR_MESSAGE_DCC_EXPIRED + ), + DCC_COMP_412( + "DCC Components request failed with error 412: Test result not yet received", + ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE + ), + DCC_COMP_500( + "DCC Test Certificate Components failed with error 500: Internal server error.", + ERROR_MESSAGE_TRY_AGAIN + ), + // TODO error message not defined + DCC_COMP_500_LAB_INVALID_RESPONSE( + "DCC Components failed with error 500: Lab Invalid response", + ERROR_MESSAGE_GENERIC + ), + // TODO error message not defined + DCC_COMP_500_SIGNING_CLIENT_ERROR( + "DCC Components failed with error 500: Signing client error", + ERROR_MESSAGE_GENERIC + ), + // TODO error message not defined + DCC_COMP_500_SIGNING_SERVER_ERROR( + "DCC Components failed with error 500: Signing server error", + ERROR_MESSAGE_GENERIC + ), + DCC_COMP_NO_NETWORK( + "DCC Test Certificate Components failed due to no network connection.", + ERROR_MESSAGE_NO_NETWORK + ), + PKR_400( + "Public Key Registration failed with 400: " + + "Bad request (e.g. wrong format of registration token or public key).", + ERROR_MESSAGE_CLIENT_ERROR_CALL_HOTLINE + ), + PKR_403( + "Public Key Registration failed with 403: Registration token is not allowed to issue a DCC.", + ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE + ), + PKR_404( + "Public Key Registration failed with 404: Registration token does not exist.", + ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE + ), + PKR_409( + "Public Key Registration failed with 409: Registration token is already assigned to a public key.", + ERROR_MESSAGE_GENERIC + ), + PKR_500( + "Public Key Registration failed with 500: Internal server error.", + ERROR_MESSAGE_TRY_AGAIN + ), + PKR_FAILED( + "Private key request failed.", + ERROR_MESSAGE_TRY_AGAIN + ), + PKR_NO_NETWORK( + "Private key request failed due to no network connection.", + ERROR_MESSAGE_NO_NETWORK + ), + } +} 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 393236da309f022d973086b833f8b50199f5b41c..29e2b32e8719f3398c3a4efb90d179f66a890801 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 @@ -15,16 +15,24 @@ interface CovidCertificateApiV1 { @POST("/version/v1/publicKey") suspend fun sendPublicKey( @Body requestBody: PublicKeyUploadRequest - ) + ): Response<Unit> data class ComponentsRequest( @SerializedName("registrationToken") val registrationToken: String, ) data class ComponentsResponse( - @SerializedName("dek") val dek: String, - @SerializedName("dcc") val dcc: String - ) + @SerializedName("dek") val dek: String? = null, + @SerializedName("dcc") val dcc: String? = null, + @SerializedName("reason") val errorReason: String? = null + ) { + enum class Reason(val errorString: String) { + SIGNING_CLIENT_ERROR("SIGNING_CLIENT_ERROR"), + SIGNING_SERVER_ERROR("SIGNING_SERVER_ERROR"), + LAB_INVALID_RESPONSE("LAB_INVALID_RESPONSE"), + INTERNAL("INTERNAL") + } + } @POST("/version/v1/publicKey") suspend fun getComponents( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateServer.kt index 9e4eed79c1466af27331882ba14c914df6b6b2e2..7d8996322f0e04cca4a0bfe690cc5675f03df599 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/CovidCertificateServer.kt @@ -3,8 +3,32 @@ package de.rki.coronawarnapp.covidcertificate.server import dagger.Lazy import dagger.Reusable import de.rki.coronawarnapp.coronatest.type.RegistrationToken +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_202 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_400 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_404 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_410 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_412 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_500 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_500_LAB_INVALID_RESPONSE +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_500_SIGNING_CLIENT_ERROR +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_500_SIGNING_SERVER_ERROR +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.DCC_COMP_NO_NETWORK +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.PKR_400 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.PKR_403 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.PKR_404 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.PKR_409 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.PKR_500 +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.PKR_FAILED +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException.ErrorCode.PKR_NO_NETWORK +import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateApiV1.ComponentsResponse.Reason.INTERNAL +import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateApiV1.ComponentsResponse.Reason.LAB_INVALID_RESPONSE +import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateApiV1.ComponentsResponse.Reason.SIGNING_CLIENT_ERROR +import de.rki.coronawarnapp.covidcertificate.server.CovidCertificateApiV1.ComponentsResponse.Reason.SIGNING_SERVER_ERROR import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.encryption.rsa.RSAKey +import de.rki.coronawarnapp.util.network.NetworkStateProvider +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject @@ -12,42 +36,77 @@ import javax.inject.Inject @Reusable class CovidCertificateServer @Inject constructor( private val dccApi: Lazy<CovidCertificateApiV1>, - private val dispatcherProvider: DispatcherProvider + private val dispatcherProvider: DispatcherProvider, + private val networkStateProvider: NetworkStateProvider ) { private val api: CovidCertificateApiV1 get() = dccApi.get() + @Throws(TestCertificateServerException::class) suspend fun registerPublicKeyForTest( testRegistrationToken: RegistrationToken, publicKey: RSAKey.Public, ): Unit = withContext(dispatcherProvider.IO) { Timber.tag(TAG).v("registerPublicKeyForTest(token=%s, key=%s)", testRegistrationToken, publicKey) - api.sendPublicKey( - requestBody = CovidCertificateApiV1.PublicKeyUploadRequest( - registrationToken = testRegistrationToken, - publicKey = publicKey.base64 + if (!isInternetAvailable()) { + throw TestCertificateServerException(PKR_NO_NETWORK) + } + try { + val response = api.sendPublicKey( + requestBody = CovidCertificateApiV1.PublicKeyUploadRequest( + registrationToken = testRegistrationToken, + publicKey = publicKey.base64 + ) ) - ) + when (response.code()) { + 400 -> throw TestCertificateServerException(PKR_400) + 403 -> throw TestCertificateServerException(PKR_403) + 404 -> throw TestCertificateServerException(PKR_404) + 409 -> throw TestCertificateServerException(PKR_409) + 500 -> throw TestCertificateServerException(PKR_500) + } + } catch (e: Throwable) { + throw TestCertificateServerException(PKR_FAILED) + } } - @Throws(DccException::class) + @Throws(TestCertificateServerException::class) suspend fun requestCertificateForTest( testRegistrationToken: RegistrationToken, ): TestCertificateComponents = withContext(dispatcherProvider.IO) { Timber.tag(TAG).v("requestCertificateForTest(token=%s)", testRegistrationToken) + if (!isInternetAvailable()) { + throw TestCertificateServerException(DCC_COMP_NO_NETWORK) + } val response = api.getComponents( requestBody = CovidCertificateApiV1.ComponentsRequest(testRegistrationToken) ) - // TODO replace with InvalidTestCertificateException + correct error codes - if (response.code() == 202) throw DccException() - val result = response.body() ?: throw DccException() + when (response.code()) { + 202 -> throw TestCertificateServerException(DCC_COMP_202) + 400 -> throw TestCertificateServerException(DCC_COMP_400) + 404 -> throw TestCertificateServerException(DCC_COMP_404) + 410 -> throw TestCertificateServerException(DCC_COMP_410) + 412 -> throw TestCertificateServerException(DCC_COMP_412) + 500 -> when (response.body()?.errorReason) { + INTERNAL.errorString -> throw TestCertificateServerException(DCC_COMP_500) + SIGNING_CLIENT_ERROR.errorString -> + throw TestCertificateServerException(DCC_COMP_500_SIGNING_CLIENT_ERROR) + SIGNING_SERVER_ERROR.errorString -> + throw TestCertificateServerException(DCC_COMP_500_SIGNING_SERVER_ERROR) + LAB_INVALID_RESPONSE.errorString -> + throw TestCertificateServerException(DCC_COMP_500_LAB_INVALID_RESPONSE) + } + } + val result = response.body()!! // throw exception? TestCertificateComponents( - dataEncryptionKeyBase64 = result.dek, - encryptedCoseTestCertificateBase64 = result.dcc + dataEncryptionKeyBase64 = result.dek!!, + encryptedCoseTestCertificateBase64 = result.dcc!! ) } + private suspend fun isInternetAvailable() = networkStateProvider.networkState.first().isInternetAvailable + companion object { private const val TAG = "CovidCertificateServer" } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/DccException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/DccException.kt deleted file mode 100644 index 058cb8292f375a08f757561e70e9b37adfc9ac5a..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/server/DccException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package de.rki.coronawarnapp.covidcertificate.server - -class DccException : Exception() 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 2fd765e1e11d46b2397d5bb5f68b64ab96d8c518..f084c7673186622b6c24275bf78e9fb484bdd3a6 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 @@ -6,7 +6,6 @@ import de.rki.coronawarnapp.covidcertificate.exception.InvalidHealthCertificateE import de.rki.coronawarnapp.covidcertificate.exception.InvalidHealthCertificateException.ErrorCode.AES_DECRYPTION_FAILED import de.rki.coronawarnapp.covidcertificate.exception.InvalidHealthCertificateException.ErrorCode.HC_COSE_MESSAGE_INVALID import de.rki.coronawarnapp.covidcertificate.exception.InvalidHealthCertificateException.ErrorCode.HC_COSE_TAG_INVALID -import de.rki.coronawarnapp.util.encoding.base64 import timber.log.Timber import javax.inject.Inject @@ -41,7 +40,7 @@ class HealthCertificateCOSEDecoder @Inject constructor( private fun ByteArray.decrypt(decryptionKey: ByteArray) = try { aesEncryptor.decrypt( decryptionKey = decryptionKey, - encryptedData = this.base64().toByteArray() + encryptedData = this ) } catch (e: Throwable) { Timber.e(e) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptographyTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptographyTest.kt index 1d9ecfde2fe05727f63a3641a4a85efc46c44e39..b98b171ad5c9667de587c4dd45a260678d4fcab4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptographyTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/cryptography/AesCryptographyTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.covidcertificate.cryptography import io.kotest.matchers.shouldBe +import okio.ByteString.Companion.decodeBase64 import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -8,8 +9,8 @@ class AesCryptographyTest : BaseTest() { @Test fun `decrypt Hello World`() { - val des = "d56t/juMw5r4qNx1n1igs1pobUjZBT5yq0Ct7MHUuKM=".toByteArray() - val encryptedString = "WFOLewp8DWqY/8IWUHEDwg==".toByteArray() + val des = "d56t/juMw5r4qNx1n1igs1pobUjZBT5yq0Ct7MHUuKM=".decodeBase64()!!.toByteArray() + val encryptedString = "WFOLewp8DWqY/8IWUHEDwg==".decodeBase64()!!.toByteArray() AesCryptography().decrypt( des, encryptedData = encryptedString diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateQRCodeExtractorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateQRCodeExtractorTest.kt index ef3f817acac28c8d695179985a117937c7e2b5a3..0fa2e01caf16a8d470df2468c735bbb70b5894ab 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateQRCodeExtractorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateQRCodeExtractorTest.kt @@ -59,7 +59,7 @@ class TestCertificateQRCodeExtractorTest : BaseTest() { fun `happy path cose decryption with Ellen Cheng`() { with(TestData.EllenCheng()) { val coseObject = coseWithEncryptedPayload.decodeBase64()!!.toByteArray() - val dek = dek.toByteArray() + val dek = dek.decodeBase64()!!.toByteArray() val result = extractor.extract(dek, coseObject) with(result.testCertificateData.certificate.nameData) { familyName shouldBe "Cheng" @@ -77,7 +77,7 @@ class TestCertificateQRCodeExtractorTest : BaseTest() { fun `happy path cose decryption with Brian Calamandrei`() { with(TestData.BrianCalamandrei()) { val coseObject = coseWithEncryptedPayload.decodeBase64()!!.toByteArray() - val dek = dek.toByteArray() + val dek = dek.decodeBase64()!!.toByteArray() val result = extractor.extract(dek, coseObject) with(result.testCertificateData.certificate.nameData) { familyName shouldBe "Calamandrei"