Skip to content

Commit

Permalink
Updated logic to show specific error for the case signing certificate…
Browse files Browse the repository at this point in the history
… has been expired (#160)

* Updated logic to show specific error for the case signing certificate has been expired

* Updated tests
  • Loading branch information
oleksandrsarapulovgl authored Jul 22, 2021
1 parent bf65f61 commit 2676aa6
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 153 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* ---license-start
* eu-digital-green-certificates / dgca-verifier-app-android
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ---license-end
*
* Created by osarapulov on 7/22/21 11:44 AM
*/

package dgca.verifier.app.android.verification

import dgca.verifier.app.decoder.cbor.GreenCertificateData

data class InnerVerificationResult(
val noPublicKeysFound: Boolean = true,
val certificateExpired: Boolean = false,
val greenCertificateData: GreenCertificateData? = null,
val isApplicableCode: Boolean = false,
val base64EncodedKid: String? = null
) {
fun isValid() =
!noPublicKeysFound && !certificateExpired && greenCertificateData != null && isApplicableCode && base64EncodedKid?.isNotBlank() == true
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,73 +94,11 @@ class VerificationDialogFragment : BottomSheetDialogFragment() {
binding.rulesList.layoutManager = LinearLayoutManager(requireContext())
binding.actionBtn.setOnClickListener { dismiss() }

viewModel.verificationData.observe(viewLifecycleOwner, {
if (it.verificationResult == null) {
viewModel.verificationData.observe(viewLifecycleOwner, { verificationData ->
if (verificationData.verificationResult == null) {
hideLiveData.value = null
} else {
setCertStatusUI(it.verificationResult.getGeneralResult())
setCertDataVisibility(it.verificationResult.getGeneralResult())
it.certificateModel?.let { certificateModel ->
binding.personFullName.text = certificateModel.getFullName()
toggleButton(certificateModel)


// TODO remove before release
if (it.verificationResult.getGeneralResult() == GeneralVerificationResult.SUCCESS) {
val ruleValidationResultCards = mutableListOf<RuleValidationResultCard>()
val context = requireContext()
binding.rulesList.visibility = View.VISIBLE
viewModel.validationResults.value?.forEach {
ruleValidationResultCards.add(
it.toRuleValidationResultCard(context)
)
}
binding.rulesList.adapter =
RuleValidationResultsAdapter(layoutInflater, ruleValidationResultCards)
}

if (it.verificationResult.getGeneralResult() != GeneralVerificationResult.FAILED) {
showUserData(certificateModel)

if (binding.greenCertificate.parent != null) {
when {
certificateModel.vaccinations?.size == 1 -> {
binding.greenCertificate.layoutResource =
R.layout.item_vaccination
binding.greenCertificate.setOnInflateListener { stub, inflated ->
VaccinationViewHolder.create(
inflated as ViewGroup
).bind(certificateModel.vaccinations.first())
}
binding.greenCertificate.inflate()
}
certificateModel.recoveryStatements?.size == 1 -> {
binding.greenCertificate.layoutResource = R.layout.item_recovery

binding.greenCertificate.setOnInflateListener { stub, inflated ->
RecoveryViewHolder.create(
inflated as ViewGroup
).bind(certificateModel.recoveryStatements.first())
}
binding.greenCertificate.inflate()
}
certificateModel.tests?.size == 1 -> {
binding.greenCertificate.layoutResource = R.layout.item_test

binding.greenCertificate.setOnInflateListener { stub, inflated ->
TestViewHolder.create(
inflated as ViewGroup
).bind(certificateModel.tests.first())
}
binding.greenCertificate.inflate()
}
}
}


}
}
startTimer()
handleVerificationResult(verificationData)
}
})
viewModel.verificationError.observe(viewLifecycleOwner, {
Expand All @@ -178,6 +116,72 @@ class VerificationDialogFragment : BottomSheetDialogFragment() {
_binding = null
}

private fun handleVerificationResult(verificationData: VerificationData) {
setCertStatusUI(verificationData.getGeneralResult())
setCertDataVisibility(verificationData.getGeneralResult())
verificationData.certificateModel?.let { certificateModel ->
binding.personFullName.text = certificateModel.getFullName()
toggleButton(certificateModel)


// TODO remove before release
if (verificationData.getGeneralResult() == GeneralVerificationResult.SUCCESS) {
val ruleValidationResultCards = mutableListOf<RuleValidationResultCard>()
val context = requireContext()
binding.rulesList.visibility = View.VISIBLE
viewModel.validationResults.value?.forEach { validationResult ->
ruleValidationResultCards.add(
validationResult.toRuleValidationResultCard(context)
)
}
binding.rulesList.adapter =
RuleValidationResultsAdapter(layoutInflater, ruleValidationResultCards)
}

if (verificationData.getGeneralResult() != GeneralVerificationResult.FAILED) {
showUserData(certificateModel)

if (binding.greenCertificate.parent != null) {
when {
certificateModel.vaccinations?.size == 1 -> {
binding.greenCertificate.layoutResource =
R.layout.item_vaccination
binding.greenCertificate.setOnInflateListener { stub, inflated ->
VaccinationViewHolder.create(
inflated as ViewGroup
).bind(certificateModel.vaccinations.first())
}
binding.greenCertificate.inflate()
}
certificateModel.recoveryStatements?.size == 1 -> {
binding.greenCertificate.layoutResource = R.layout.item_recovery

binding.greenCertificate.setOnInflateListener { stub, inflated ->
RecoveryViewHolder.create(
inflated as ViewGroup
).bind(certificateModel.recoveryStatements.first())
}
binding.greenCertificate.inflate()
}
certificateModel.tests?.size == 1 -> {
binding.greenCertificate.layoutResource = R.layout.item_test

binding.greenCertificate.setOnInflateListener { stub, inflated ->
TestViewHolder.create(
inflated as ViewGroup
).bind(certificateModel.tests.first())
}
binding.greenCertificate.inflate()
}
}
}


}
}
startTimer()
}

private fun setCertStatusUI(generalVerificationResult: GeneralVerificationResult) {
val text: String
val imageId: Int
Expand Down Expand Up @@ -218,9 +222,10 @@ class VerificationDialogFragment : BottomSheetDialogFragment() {
binding.reasonForCertificateInvalidityName.visibility = View.VISIBLE
binding.reasonForCertificateInvalidityName.text = getString(
when (verificationError) {
VerificationError.CERTIFICATE_EXPIRED -> R.string.certificate_is_expired
VerificationError.GREEN_CERTIFICATE_EXPIRED -> R.string.certificate_is_expired
VerificationError.CERTIFICATE_REVOKED -> R.string.certificate_was_revoked
VerificationError.VERIFICATION_FAILED -> R.string.verification_failed
VerificationError.CERTIFICATE_EXPIRED -> R.string.signing_certificate_is_expired
VerificationError.TEST_DATE_IS_IN_THE_FUTURE -> R.string.the_test_date_is_in_the_future
VerificationError.TEST_RESULT_POSITIVE -> R.string.test_result_positive
VerificationError.RECOVERY_NOT_VALID_SO_FAR -> R.string.recovery_not_valid_yet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
package dgca.verifier.app.android.verification

import dgca.verifier.app.decoder.model.VerificationResult
import dgca.verifier.app.engine.ValidationResult

enum class VerificationError {
GREEN_CERTIFICATE_EXPIRED,
CERTIFICATE_EXPIRED,
CERTIFICATE_REVOKED,
VERIFICATION_FAILED,
Expand All @@ -37,12 +37,13 @@ enum class VerificationError {
CRYPTOGRAPHIC_SIGNATURE_INVALID
}

fun VerificationResult.fetchError(noPublicKeysFound: Boolean): VerificationError? =
fun VerificationResult.fetchError(innerVerificationResult: InnerVerificationResult): VerificationError? =
when {
isValid() -> null
noPublicKeysFound-> VerificationError.VERIFICATION_FAILED
isValid() && innerVerificationResult.isValid() -> null
innerVerificationResult.noPublicKeysFound-> VerificationError.VERIFICATION_FAILED
innerVerificationResult.certificateExpired -> VerificationError.CERTIFICATE_EXPIRED
!coseVerified -> VerificationError.CRYPTOGRAPHIC_SIGNATURE_INVALID
!isNotExpired -> VerificationError.CERTIFICATE_EXPIRED
!isNotExpired -> VerificationError.GREEN_CERTIFICATE_EXPIRED
isTestDateInTheFuture() -> VerificationError.TEST_DATE_IS_IN_THE_FUTURE
isTestWithPositiveResult() -> VerificationError.TEST_RESULT_POSITIVE
isRecoveryNotValidSoFar() -> VerificationError.RECOVERY_NOT_VALID_SO_FAR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,18 @@ enum class GeneralVerificationResult {
SUCCESS, FAILED, RULES_VALIDATION_FAILED
}

fun VerificationResult.getGeneralResult(): GeneralVerificationResult {
fun VerificationData.getGeneralResult(): GeneralVerificationResult {
return when {
isValid() -> GeneralVerificationResult.SUCCESS
isTestWithPositiveResult() -> GeneralVerificationResult.FAILED
rulesValidationFailed -> GeneralVerificationResult.RULES_VALIDATION_FAILED
verificationResult?.isValid() == true && innerVerificationResult.isValid() -> GeneralVerificationResult.SUCCESS
verificationResult?.isTestWithPositiveResult() == true -> GeneralVerificationResult.FAILED
verificationResult?.rulesValidationFailed == true -> GeneralVerificationResult.RULES_VALIDATION_FAILED
else -> GeneralVerificationResult.FAILED
}
}

data class VerificationData(
val verificationResult: VerificationResult?,
val innerVerificationResult: InnerVerificationResult,
val certificateModel: CertificateModel?
)

Expand Down Expand Up @@ -129,31 +130,24 @@ class VerificationViewModel @Inject constructor(

}

verificationResult.fetchError(innerVerificationResult.noPublicKeysFound)
verificationResult.fetchError(innerVerificationResult)
?.apply { _verificationError.value = this }

_inProgress.value = false
val certificateModel: CertificateModel? =
innerVerificationResult.greenCertificateData?.greenCertificate?.toCertificateModel()
_verificationData.value = VerificationData(
if (innerVerificationResult.isApplicableCode) verificationResult else null,
innerVerificationResult,
certificateModel
)
}
}

data class InnerVerificationResult(
val noPublicKeysFound: Boolean = true,
val greenCertificateData: GreenCertificateData? = null,
val isApplicableCode: Boolean = false,
val base64EncodedKid: String? = null
)

private suspend fun validateCertificate(
code: String,
verificationResult: VerificationResult
): InnerVerificationResult {
var noPublicKeysFound = true
var greenCertificateData: GreenCertificateData? = null
var isApplicableCode = false

Expand All @@ -165,19 +159,17 @@ class VerificationViewModel @Inject constructor(
if (coseData == null) {
Timber.d("Verification failed: COSE not decoded")
return InnerVerificationResult(
noPublicKeysFound,
greenCertificateData,
isApplicableCode
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode
)
}

val kid = coseData.kid
if (kid == null) {
Timber.d("Verification failed: cannot extract kid from COSE")
return InnerVerificationResult(
noPublicKeysFound,
greenCertificateData,
isApplicableCode
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode
)
}

Expand All @@ -192,13 +184,13 @@ class VerificationViewModel @Inject constructor(
if (certificates.isEmpty()) {
Timber.d("Verification failed: failed to load certificate")
return InnerVerificationResult(
noPublicKeysFound,
greenCertificateData,
isApplicableCode,
base64EncodedKid
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode,
base64EncodedKid = base64EncodedKid
)
}
noPublicKeysFound = false
val noPublicKeysFound = false
var certificateExpired = false
certificates.forEach { innerCertificate ->
cryptoService.validate(
cose,
Expand All @@ -214,17 +206,17 @@ class VerificationViewModel @Inject constructor(
val currentTime: ZonedDateTime =
ZonedDateTime.now().withZoneSameInstant(UTC_ZONE_ID)
if (expirationTime != null && currentTime.isAfter(expirationTime)) {
noPublicKeysFound = true
verificationResult.coseVerified = false
certificateExpired = true
}
return@forEach
}
}
return InnerVerificationResult(
noPublicKeysFound,
greenCertificateData,
isApplicableCode,
base64EncodedKid
noPublicKeysFound = noPublicKeysFound,
certificateExpired = certificateExpired,
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode,
base64EncodedKid = base64EncodedKid
)
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<string name="certificate_is_expired">Certificate is expired.</string>
<string name="certificate_was_revoked">Certificate was revoked.</string>
<string name="verification_failed">Verification failed.</string>
<string name="signing_certificate_is_expired">Signing Certificate is expired.</string>
<string name="the_test_date_is_in_the_future">The test date is in the future.</string>
<string name="test_result_positive">Test result positive.</string>
<string name="recovery_not_valid_yet">Recovery statement is not valid yet.</string>
Expand Down
Loading

0 comments on commit 2676aa6

Please sign in to comment.