Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Release 5.7.6 (unreleased)
* Update to VC-K 5.11.0
* Credentials: Fix displaying age verification credential
* Support URL scheme `av` for age verification

Expand Down
1 change: 1 addition & 0 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ kotlin {
implementation(libs.datastore.preferences.core)
}
}
jvmToolchain(17)
}

val apkSignerPassword =
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
val testballoonVer =
System.getenv("TESTBALLOON_VERSION_OVERRIDE")?.ifBlank { null } ?: libs.versions.testballoon.get()

id("at.asitplus.gradle.conventions") version "20251023"
id("at.asitplus.gradle.conventions") version "20251217"
kotlin("multiplatform") version kotlinVer apply false
kotlin("plugin.serialization") version kotlinVer apply false
id("de.infix.testBalloon") version testballoonVer apply false
Expand Down
9 changes: 5 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[versions]
vck="5.10.1"
vck="5.11.0-SNAPSHOT"

agp = "8.12.3"
kotlin = "2.2.21"
kotlin = "2.3.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these version declarations still used?

compose-plugin = "1.8.2"
koin = "4.1.0"
testballoon = "0.7.1-K2.2.21"
koin = "4.1.1"
testballoon = "0.7.1-K2.3.0"

accompanist-permissions = "0.37.3"
activity-compose = "1.10.1"
Expand Down Expand Up @@ -76,6 +76,7 @@ koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
koin-compose-viewmodel-navigation = { module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin" }
testballoon = { module = "de.infix.testBalloon:testBalloon-framework-shared", version.ref = "testballoon" }
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
qrose = { group = "io.github.alexzhirkevich", name = "qrose", version.ref="qrose"}
multipaz = { module = "org.multipaz:multipaz", version.ref = "multipaz" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#Tue May 13 11:32:52 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pluginManagement {
}

plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version ("0.4.0")
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}


Expand Down
17 changes: 6 additions & 11 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ kotlin {
}
}
}

}
jvmToolchain(17)

if ("true" != disableAppleTargets) {
iosX64()
Expand Down Expand Up @@ -118,7 +118,7 @@ kotlin {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.uiTest)
implementation(libs.koin.test)
implementation("de.infix.testBalloon:testBalloon-framework-shared:0.7.1-K2.2.21")
implementation(libs.testballoon)
}

androidMain.dependencies {
Expand Down Expand Up @@ -184,14 +184,9 @@ exportXCFramework(
freeCompilerArgs += listOf("-Xoverride-konan-properties=minVersion.ios=18.5;minVersionSinceXcode15.ios=18.5")
}

tasks.register("iosBootSimulator") {
doLast {
exec {
isIgnoreExitValue = true
runCatching {
commandLine("xcrun", "simctl", "boot", "iPhone 16")
}
}
tasks.register<Exec>("iosBootSimulator") {
runCatching {
commandLine("xcrun", "simctl", "boot", "iPhone 16")
}
}

Expand All @@ -206,7 +201,7 @@ if ("true" != disableAppleTargets) {
tasks.register("findDependency") {
group = "help"
description = "Lists every configuration that resolves the given module"
val target = project.providers.gradleProperty("module").forUseAtConfigurationTime()
val target = project.providers.gradleProperty("module")

doLast {
val wanted = target.getOrElse("").takeIf { it.isNotBlank() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class RealCapabilitiesService(

private suspend fun getSignerStatus() = keyStoreService.testSigner()

private suspend fun getAttestationStatus() =
private suspend fun getAttestationStatus(): Boolean =
getAttestationPreference().onSuccess {
return true
}.onFailure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,7 @@ fun ConstantIndex.CredentialScheme.toJsonElement(
representation: CredentialRepresentation,
requestedElements: Collection<String>? = null
): JsonElement {
val dataElements = when (this) {
EuPidScheme -> this.claimNames + EuPidScheme.Attributes.PORTRAIT_CAPTURE_DATE
ConstantIndex.AtomicAttribute2023, IdAustriaScheme, EuPidSdJwtScheme, MobileDrivingLicenceScheme, AgeVerificationScheme, HealthIdScheme, EhicScheme, TaxIdScheme -> this.claimNames
is VcFallbackCredentialScheme, is SdJwtFallbackCredentialScheme, is IsoMdocFallbackCredentialScheme -> this.claimNames
else -> TODO("${this::class.simpleName} not implemented in jsonElementBuilder yet")
}

val dataElements = this.claimNames
// TODO move this to credentials libraries
val complexElements = when (this) {
EuPidSdJwtScheme -> buildJsonObject {
Expand All @@ -172,21 +166,6 @@ fun ConstantIndex.CredentialScheme.toJsonElement(
put(HOUSE_NUMBER, JsonPrimitive(""))
}
})
put(EuPidSdJwtScheme.SdJwtAttributes.PREFIX_AGE_EQUAL_OR_OVER, buildJsonObject {
with(EuPidSdJwtScheme.SdJwtAttributes.AgeEqualOrOver) {
put(EQUAL_OR_OVER_12, JsonPrimitive(""))
put(EQUAL_OR_OVER_13, JsonPrimitive(""))
put(EQUAL_OR_OVER_14, JsonPrimitive(""))
put(EQUAL_OR_OVER_16, JsonPrimitive(""))
put(EQUAL_OR_OVER_18, JsonPrimitive(""))
put(EQUAL_OR_OVER_21, JsonPrimitive(""))
put(EQUAL_OR_OVER_25, JsonPrimitive(""))
put(EQUAL_OR_OVER_60, JsonPrimitive(""))
put(EQUAL_OR_OVER_62, JsonPrimitive(""))
put(EQUAL_OR_OVER_65, JsonPrimitive(""))
put(EQUAL_OR_OVER_68, JsonPrimitive(""))
}
})
put(EuPidSdJwtScheme.SdJwtAttributes.PREFIX_PLACE_OF_BIRTH, buildJsonObject {
with(EuPidSdJwtScheme.SdJwtAttributes.PlaceOfBirth) {
put(COUNTRY, JsonPrimitive(""))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class SigningService(
val credentialInfo = config.getCurrent().credentialInfo?.toSigningCredential() ?: throw Throwable("Missing credentialInfo")

val signAlgorithm =
credentialInfo.supportedSigningAlgorithms?.first() ?: X509SignatureAlgorithm.RS512
credentialInfo.supportedSigningAlgorithms.first() ?: X509SignatureAlgorithm.RS512

val signHashRequest = rqesWalletService.createSignHashRequestParameters(
dtbsr = (this.dtbsrAuthenticationDetails as CscAuthorizationDetails).documentDigests.map { it.hash },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ fun tokenStatusListModule() = module {
),
clock = Clock.System,
getCachingDuration = { (key, value) ->
val payload = value.payload.getOrNull()
listOfNotNull(
value.payload.expirationTime?.let { it - Clock.System.now() },
value.payload.timeToLive?.duration
payload?.expirationTime?.let { it - Clock.System.now() },
payload?.timeToLive?.duration
).minOrNull()?.also {
Napier.d("Entry specific caching duration is used: $it")
} ?: 300.seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ abstract class CredentialAdapter {
protected fun JsonElement?.content() =
(this as? JsonPrimitive)?.contentOrNull ?: (this as? JsonArray)?.joinToString()

protected fun JsonPrimitive?.toCollectionOrNull() =
(this as? JsonArray)?.let { it.map { it.toString() } }

protected fun JsonElement?.toCollectionOrNull() =
(this as? JsonArray)?.let { it.map { it.content() ?: it.toString() } }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ private class EuPidCredentialSdJwtAdapter(

override val nationalities: Collection<String>?
get() = complexJson?.get(SdJwtAttributes.NATIONALITIES)?.toCollectionOrNull()
?: attributes[SdJwtAttributes.NATIONALITIES]?.toCollectionOrNull()
?: attributes[SdJwtAttributes.NATIONALITIES]?.let { listOfNotNull(it.contentOrNull) }?.ifEmpty { null }
?: listOfNotNull(nationality).ifEmpty { null }

override val ageInYears: UInt?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,24 +93,6 @@ object EuPidSdJwtCredentialAttributeCategorization : CredentialAttributeCategori
).map { NormalizedJsonPath() + it to null }
},

PersonalDataCategory.AgeData to with(EuPidSdJwtScheme.SdJwtAttributes) {
listOf(
AGE_EQUAL_OR_OVER_12,
AGE_EQUAL_OR_OVER_13,
AGE_EQUAL_OR_OVER_14,
AGE_EQUAL_OR_OVER_16,
AGE_EQUAL_OR_OVER_18,
AGE_EQUAL_OR_OVER_21,
AGE_EQUAL_OR_OVER_25,
AGE_EQUAL_OR_OVER_60,
AGE_EQUAL_OR_OVER_62,
AGE_EQUAL_OR_OVER_65,
AGE_EQUAL_OR_OVER_68,
AGE_BIRTH_YEAR,
AGE_IN_YEARS,
).map { NormalizedJsonPath() + it to null }
},

PersonalDataCategory.BirthData to with(EuPidSdJwtScheme.SdJwtAttributes) {
listOf(
GIVEN_NAME_BIRTH,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,6 @@ class EuPidCredentialSdJwtClaimDefinitionResolver {
GIVEN_NAME -> EuPidCredentialClaimDefinition.GIVEN_NAME
BIRTH_DATE -> EuPidCredentialClaimDefinition.BIRTH_DATE
PORTRAIT -> EuPidCredentialClaimDefinition.PORTRAIT
PREFIX_AGE_EQUAL_OR_OVER -> when (val second = path.segments.getOrNull(1)) {
is NormalizedJsonPathSegment.NameSegment -> when (second.memberName) {
AgeEqualOrOver.EQUAL_OR_OVER_12 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_12
AgeEqualOrOver.EQUAL_OR_OVER_13 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_13
AgeEqualOrOver.EQUAL_OR_OVER_14 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_14
AgeEqualOrOver.EQUAL_OR_OVER_16 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_16
AgeEqualOrOver.EQUAL_OR_OVER_18 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_18
AgeEqualOrOver.EQUAL_OR_OVER_21 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_21
AgeEqualOrOver.EQUAL_OR_OVER_25 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_25
AgeEqualOrOver.EQUAL_OR_OVER_60 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_60
AgeEqualOrOver.EQUAL_OR_OVER_62 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_62
AgeEqualOrOver.EQUAL_OR_OVER_65 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_65
AgeEqualOrOver.EQUAL_OR_OVER_68 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_68
else -> null
}
null -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_CONTAINER
else -> null
}
// TODO: are those deprecated? accessing without age prefix seems weird
AGE_EQUAL_OR_OVER_12 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_12
AGE_EQUAL_OR_OVER_13 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_13
AGE_EQUAL_OR_OVER_14 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_14
AGE_EQUAL_OR_OVER_16 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_16
AGE_EQUAL_OR_OVER_18 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_18
AGE_EQUAL_OR_OVER_21 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_21
AGE_EQUAL_OR_OVER_25 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_25
AGE_EQUAL_OR_OVER_60 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_60
AGE_EQUAL_OR_OVER_62 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_62
AGE_EQUAL_OR_OVER_65 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_65
AGE_EQUAL_OR_OVER_68 -> EuPidCredentialClaimDefinition.AGE_EQUAL_OR_OVER_68
AGE_IN_YEARS -> EuPidCredentialClaimDefinition.AGE_IN_YEARS
AGE_BIRTH_YEAR -> EuPidCredentialClaimDefinition.AGE_BIRTH_YEAR
FAMILY_NAME_BIRTH -> EuPidCredentialClaimDefinition.FAMILY_NAME_BIRTH
GIVEN_NAME_BIRTH -> EuPidCredentialClaimDefinition.GIVEN_NAME_BIRTH
PREFIX_PLACE_OF_BIRTH -> when (val second = path.segments.getOrNull(1)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import at.asitplus.valera.resources.text_label_valid_from
import at.asitplus.valera.resources.text_label_valid_to
import at.asitplus.valera.resources.text_label_vcType
import at.asitplus.wallet.lib.agent.SubjectCredentialStore.StoreEntry
import at.asitplus.wallet.lib.data.Status
import at.asitplus.wallet.mdl.DrivingPrivilege
import data.credentials.CredentialAdapter.Companion.toComplexJson
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
Expand Down Expand Up @@ -127,9 +126,9 @@ private fun SingleVcCredentialCardContent(
ElevatedCard(modifier = Modifier.padding(bottom = 16.dp)) {
Column(modifier = Modifier.fillMaxWidth()) {
TechnicalMetadataHeader()
credential.vc.vc.credentialStatus?.let {
StatusUri(it, modifier)
StatusIndex(it, modifier)
credential.vc.vc.credentialStatus?.statusList?.let {
StatusUri(modifier, it.uri.string)
StatusIndex(modifier, it.index)
}
ValidFrom(credential.vc.notBefore, modifier)
credential.vc.expiration?.let {
Expand All @@ -152,9 +151,9 @@ private fun SingleSdJwtCredentialCardContent(
ElevatedCard(modifier = Modifier.padding(bottom = 16.dp)) {
Column(modifier = Modifier.fillMaxWidth()) {
TechnicalMetadataHeader()
credential.sdJwt.credentialStatus?.let {
StatusUri(it, modifier)
StatusIndex(it, modifier)
credential.sdJwt.credentialStatus?.statusList?.let {
StatusUri(modifier, it.uri.string)
StatusIndex(modifier, it.index)
}
(credential.sdJwt.notBefore ?: credential.sdJwt.issuedAt)?.let {
ValidFrom(it, modifier)
Expand Down Expand Up @@ -195,9 +194,9 @@ private fun SingleIsoCredentialCardContent(
ElevatedCard(modifier = Modifier.padding(bottom = 16.dp)) {
Column(modifier = Modifier.fillMaxWidth()) {
TechnicalMetadataHeader()
credential.issuerSigned.issuerAuth.payload?.status?.let {
StatusUri(it, modifier)
StatusIndex(it, modifier)
credential.issuerSigned.issuerAuth.payload?.status?.statusList?.let {
StatusUri(modifier, it.uri.string)
StatusIndex(modifier, it.index)
}
credential.issuerSigned.issuerAuth.payload?.validityInfo?.let {
ValidFrom(it.validFrom, modifier)
Expand Down Expand Up @@ -249,14 +248,14 @@ private fun TechnicalMetadataHeader() {

@Composable
private fun StatusIndex(
status: Status,
modifier: Modifier
modifier: Modifier,
index: ULong
) {
LabeledContent(
label = stringResource(Res.string.text_label_status_idx),
content = {
Text(
status.statusList.index.toString(),
index.toString(),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Expand All @@ -267,14 +266,14 @@ private fun StatusIndex(

@Composable
private fun StatusUri(
status: Status,
modifier: Modifier
modifier: Modifier,
urlString: String
) {
LabeledContent(
label = stringResource(Res.string.text_label_status_uri),
content = {
Text(
status.statusList.uri.string,
urlString,
softWrap = true,
)
},
Expand Down
2 changes: 1 addition & 1 deletion shared/src/commonMain/kotlin/ui/views/ErrorView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ fun ErrorView(
.background(color = MaterialTheme.colorScheme.tertiaryContainer)
) {
Text(
vm.message ?: "Unknown Message",
vm.message,
modifier = Modifier.padding(
top = 5.dp,
bottom = 5.dp,
Expand Down