Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extracting of issuing country from certificate issuer #235

Merged
merged 2 commits into from
Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,18 @@ import javax.inject.Inject

@HiltViewModel
class VerificationViewModel @Inject constructor(
private val prefixValidationService: PrefixValidationService,
private val base45Service: Base45Service,
private val compressorService: CompressorService,
private val cryptoService: CryptoService,
private val coseService: CoseService,
private val schemaValidator: SchemaValidator,
private val cborService: CborService,
private val verifierRepository: VerifierRepository,
private val engine: CertLogicEngine,
private val getRulesUseCase: GetRulesUseCase,
private val valueSetsRepository: ValueSetsRepository,
private val preferences: Preferences
private val prefixValidationService: PrefixValidationService,
private val base45Service: Base45Service,
private val compressorService: CompressorService,
private val cryptoService: CryptoService,
private val coseService: CoseService,
private val schemaValidator: SchemaValidator,
private val cborService: CborService,
private val verifierRepository: VerifierRepository,
private val engine: CertLogicEngine,
private val getRulesUseCase: GetRulesUseCase,
private val valueSetsRepository: ValueSetsRepository,
private val preferences: Preferences
) : ViewModel() {

private val _qrCodeVerificationResult = MutableLiveData<QrCodeVerificationResult>()
Expand All @@ -86,48 +86,49 @@ class VerificationViewModel @Inject constructor(
decode(qrCodeText, countryIsoCode)
}

private fun decode(code: String, countryIsoCode: String) {
private fun decode(code: String, countryOfArrivalIsoCode: String) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val verificationResult = VerificationResult()
val innerVerificationResult: InnerVerificationResult = validateCertificate(code, verificationResult)

val validationResults: List<ValidationResult>? =
if (verificationResult.isValid() && innerVerificationResult.base64EncodedKid?.isNotBlank() == true) {
innerVerificationResult.greenCertificateData?.validateRules(
verificationResult,
countryIsoCode,
innerVerificationResult.base64EncodedKid
)
} else {
null
}
if (verificationResult.isValid() && innerVerificationResult.base64EncodedKid?.isNotBlank() == true) {
innerVerificationResult.greenCertificateData?.validateRules(
verificationResult,
innerVerificationResult.certificateIssuingCountryIsoCode.orEmpty(),
countryOfArrivalIsoCode,
innerVerificationResult.base64EncodedKid
)
} else {
null
}

if (innerVerificationResult.isApplicableCode) {
val covidCertificate = innerVerificationResult.greenCertificateData?.greenCertificate
val certificateModel = covidCertificate?.toCertificateModel()
val hcert: String? = innerVerificationResult.greenCertificateData?.hcertJson
val standardizedVerificationResult: StandardizedVerificationResult =
extractStandardizedVerificationResultFrom(
verificationResult,
innerVerificationResult
)
extractStandardizedVerificationResultFrom(
verificationResult,
innerVerificationResult
)

val isDebugModeEnabled =
standardizedVerificationResult.category != StandardizedVerificationResultCategory.VALID
&& (preferences.debugModeState?.let { DebugModeState.valueOf(it) }
?: DebugModeState.OFF) != DebugModeState.OFF
&& preferences.debugModeSelectedCountriesCodes?.contains(
innerVerificationResult.greenCertificateData?.getNormalizedIssuingCountry()
) == true
standardizedVerificationResult.category != StandardizedVerificationResultCategory.VALID
&& (preferences.debugModeState?.let { DebugModeState.valueOf(it) }
?: DebugModeState.OFF) != DebugModeState.OFF
&& preferences.debugModeSelectedCountriesCodes?.contains(
innerVerificationResult.greenCertificateData?.getNormalizedIssuingCountry()
) == true

QrCodeVerificationResult.Applicable(
standardizedVerificationResult,
certificateModel,
hcert,
validationResults?.toRuleValidationResultModels(),
isDebugModeEnabled,
innerVerificationResult.debugData
standardizedVerificationResult,
certificateModel,
hcert,
validationResults?.toRuleValidationResultModels(),
isDebugModeEnabled,
innerVerificationResult.debugData
)
} else {
QrCodeVerificationResult.NotApplicable
Expand All @@ -139,8 +140,8 @@ class VerificationViewModel @Inject constructor(
}

private suspend fun validateCertificate(
code: String,
verificationResult: VerificationResult
code: String,
verificationResult: VerificationResult
): InnerVerificationResult {
var greenCertificateData: GreenCertificateData? = null
var isApplicableCode = false
Expand All @@ -152,26 +153,26 @@ class VerificationViewModel @Inject constructor(
if (cose == null) {
Timber.d("Verification failed: Too many bytes read")
return InnerVerificationResult(
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode
)
}

val coseData = coseService.decode(cose, verificationResult)
if (coseData == null) {
Timber.d("Verification failed: COSE not decoded")
return InnerVerificationResult(
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode
)
}

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

Expand All @@ -186,27 +187,28 @@ class VerificationViewModel @Inject constructor(
if (certificates.isEmpty()) {
Timber.d("Verification failed: failed to load certificate")
return InnerVerificationResult(
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode,
base64EncodedKid = base64EncodedKid
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode,
base64EncodedKid = base64EncodedKid
)
}
val noPublicKeysFound = false
var certificateExpired = false
var issuingCountry = ""
certificates.forEach { innerCertificate ->
cryptoService.validate(
cose,
innerCertificate,
verificationResult,
greenCertificateData?.greenCertificate?.getType()
?: dgca.verifier.app.decoder.model.CertificateType.UNKNOWN
cose,
innerCertificate,
verificationResult,
greenCertificateData?.greenCertificate?.getType()
?: dgca.verifier.app.decoder.model.CertificateType.UNKNOWN
)

if (verificationResult.coseVerified) {
val expirationTime: ZonedDateTime? = if (innerCertificate is X509Certificate) {
innerCertificate.notAfter.toInstant().atZone(UTC_ZONE_ID)
} else {
null
var expirationTime: ZonedDateTime? = null
if (innerCertificate is X509Certificate) {
expirationTime = innerCertificate.notAfter.toInstant().atZone(UTC_ZONE_ID)
issuingCountry = innerCertificate.getIssuerCountry()
}

val currentTime: ZonedDateTime = ZonedDateTime.now().withZoneSameInstant(UTC_ZONE_ID)
Expand All @@ -220,29 +222,36 @@ class VerificationViewModel @Inject constructor(
}

return InnerVerificationResult(
noPublicKeysFound = noPublicKeysFound,
certificateExpired = certificateExpired,
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode,
base64EncodedKid = base64EncodedKid,
debugData = DebugData(code, cose, coseData.cbor)
noPublicKeysFound = noPublicKeysFound,
certificateExpired = certificateExpired,
certificateIssuingCountryIsoCode = issuingCountry,
greenCertificateData = greenCertificateData,
isApplicableCode = isApplicableCode,
base64EncodedKid = base64EncodedKid,
debugData = DebugData(code, cose, coseData.cbor)
)
}

private suspend fun GreenCertificateData.validateRules(
verificationResult: VerificationResult,
countryIsoCode: String,
base64EncodedKid: String
verificationResult: VerificationResult,
certificateIssuingCountryIsoCode: String,
countryOfArrivalIsoCode: String,
base64EncodedKid: String
): List<ValidationResult>? {
this.apply {
val engineCertificateType = this.greenCertificate.getEngineCertificateType()
return if (countryIsoCode.isNotBlank()) {
val issuingCountry: String = this.getNormalizedIssuingCountry()

return if (countryOfArrivalIsoCode.isNotBlank()) {
val issuingCountry: String = if (certificateIssuingCountryIsoCode.isNotBlank()) {
certificateIssuingCountryIsoCode
} else {
this.getNormalizedIssuingCountry()
}
val rules = getRulesUseCase.invoke(
ZonedDateTime.now().withZoneSameInstant(UTC_ZONE_ID),
countryIsoCode,
issuingCountry,
engineCertificateType
ZonedDateTime.now().withZoneSameInstant(UTC_ZONE_ID),
countryOfArrivalIsoCode,
issuingCountry,
engineCertificateType
)
val valueSetsMap = mutableMapOf<String, List<String>>()
valueSetsRepository.getValueSets().forEach { valueSet ->
Expand All @@ -254,21 +263,21 @@ class VerificationViewModel @Inject constructor(
}

val externalParameter = ExternalParameter(
validationClock = ZonedDateTime.now(ZoneId.of(ZoneOffset.UTC.id)),
valueSets = valueSetsMap,
countryCode = countryIsoCode,
exp = this.expirationTime,
iat = this.issuedAt,
issuerCountryCode = issuingCountry,
kid = base64EncodedKid,
region = "",
validationClock = ZonedDateTime.now(ZoneId.of(ZoneOffset.UTC.id)),
valueSets = valueSetsMap,
countryCode = countryOfArrivalIsoCode,
exp = this.expirationTime,
iat = this.issuedAt,
issuerCountryCode = issuingCountry,
kid = base64EncodedKid,
region = "",
)
val validationResults = engine.validate(
engineCertificateType,
this.greenCertificate.schemaVersion,
rules,
externalParameter,
this.hcertJson
engineCertificateType,
this.greenCertificate.schemaVersion,
rules,
externalParameter,
this.hcertJson
)

validationResults.forEach {
Expand Down Expand Up @@ -297,8 +306,8 @@ class VerificationViewModel @Inject constructor(
companion object {

fun validateCertData(
certificate: GreenCertificate?,
verificationResult: VerificationResult
certificate: GreenCertificate?,
verificationResult: VerificationResult
) {
certificate?.vaccinations?.let {
if (it.isNotEmpty()) {
Expand All @@ -317,12 +326,25 @@ class VerificationViewModel @Inject constructor(
if (it.isNotEmpty()) {
val recovery = it.first()
verificationResult.recoveryVerification =
RecoveryVerificationResult(
recovery.isCertificateNotValidSoFar() == true,
recovery.isCertificateNotValidAnymore() == true
)
RecoveryVerificationResult(
recovery.isCertificateNotValidSoFar() == true,
recovery.isCertificateNotValidAnymore() == true
)
}
}
}
}
}

const val ISSUING_COUNTRY_X509_CERTIFICATE_KEY = "C"

fun X509Certificate.getIssuerCountry(): String {
val keys = issuerX500Principal.name.split(",")
keys.forEach {
val (key, value) = it.split("=")
if (key.equals(ISSUING_COUNTRY_X509_CERTIFICATE_KEY, ignoreCase = true)) {
return value.toLowerCase(Locale.ROOT)
}
}
return ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ import dgca.verifier.app.decoder.cbor.GreenCertificateData
import kotlinx.parcelize.Parcelize

data class InnerVerificationResult(
val noPublicKeysFound: Boolean = true,
val certificateExpired: Boolean = false,
val greenCertificateData: GreenCertificateData? = null,
val isApplicableCode: Boolean = false,
val base64EncodedKid: String? = null,
val debugData: DebugData? = null
val noPublicKeysFound: Boolean = true,
val certificateExpired: Boolean = false,
val certificateIssuingCountryIsoCode: String? = null,
val greenCertificateData: GreenCertificateData? = null,
val isApplicableCode: Boolean = false,
val base64EncodedKid: String? = null,
val debugData: DebugData? = null
) {
fun isValid() =
!noPublicKeysFound && !certificateExpired && greenCertificateData != null && isApplicableCode && base64EncodedKid?.isNotBlank() == true
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/src/main/java/AppConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ object Config {
const val targetSdk = 29
val javaVersion = JavaVersion.VERSION_1_8

const val versionCode = 27
const val versionName = "1.2.3"
const val versionCode = 30
const val versionName = "1.2.4"

const val androidTestInstrumentation = "androidx.test.runner.AndroidJUnitRunner"
const val proguardConsumerRules = "consumer-rules.pro"
Expand Down