diff --git a/docker-compose/README.md b/docker-compose/README.md index ef5a33df5..acade5814 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -99,14 +99,6 @@ This value will be used by reverse proxy (and services configs, if any). ## Troubleshooting ---- - -#### Display of VC verification result on success page of portal doesn't work - -We are working on fixing this issue. - ---- - #### Updating ports doesn't work Make sure the ports are also updated in: @@ -121,3 +113,14 @@ Make sure the ports are also updated in: - wallet-api/config - web.conf - db.conf + + +#### Removing the DB volume +``` +docker volume rm docker-compose_wallet-api-db +``` +#### DB Backup / Restore +``` +pg_dump -U your_user_name -h your_host -d your_db_name > backup.sql +psql -U your_user_name -h your_host -d your_db_name < backup.sql +``` \ No newline at end of file diff --git a/waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts b/waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts index 5a32617e0..6185f9231 100644 --- a/waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts +++ b/waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts @@ -47,12 +47,14 @@ kotlin { implementation("io.github.optimumcode:json-schema-validator:0.2.3") implementation(project(":waltid-libraries:credentials:waltid-verifiable-credentials")) + + // Loggin + implementation("io.github.oshai:kotlin-logging:7.0.0") } } val commonTest by getting { dependencies { implementation(kotlin("test")) - //implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0") } } val jvmTest by getting { diff --git a/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinition.kt b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinition.kt index a8a345e39..5d5af95c4 100644 --- a/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinition.kt +++ b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinition.kt @@ -4,6 +4,8 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonPrimitive @Serializable data class PresentationDefinition( @@ -64,7 +66,7 @@ data class PresentationDefinition( val name: String? = null, @SerialName("intent_to_retain") val intentToRetain: Boolean? = null, - val filter: JsonElement? = null, + val filter: JsonObject? = null, val predicate: String? = null ) @@ -86,4 +88,14 @@ data class PresentationDefinition( } } } + + fun primitiveVerificationGetTypeList(): List { + val fields = inputDescriptors.mapNotNull { it.constraints.fields }.flatten() + // filter for field paths "$.type" (W3C) or "$.vct" (sd-jwt-vc) + .filter { field -> field.path.any { "type" in it || "vct" in it } && field.filter?.get("type")?.jsonPrimitive?.contentOrNull == "string" } + + val types = fields.mapNotNull { it.filter?.get("pattern")?.jsonPrimitive?.contentOrNull } + + return types + } } diff --git a/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinitionParser.kt b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinitionParser.kt index 844706e5d..6177ec848 100644 --- a/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinitionParser.kt +++ b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationDefinitionParser.kt @@ -6,9 +6,13 @@ import id.walt.credentials.vc.vcs.W3CVC import id.walt.definitionparser.PresentationDefinition.InputDescriptor.Constraints.Field import io.github.optimumcode.json.schema.JsonSchema import io.github.optimumcode.json.schema.OutputCollector +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject +private val log = KotlinLogging.logger { } + class JsonObjectEnquirer { private val compiledJsonPaths = HashMap() @@ -27,7 +31,10 @@ class JsonObjectEnquirer { } else { if (field.filter != null) { val schema = JsonSchema.fromJsonElement(field.filter) - schema.validate(resolvedPath, OutputCollector.flag()).valid + when(resolvedPath) { + is JsonArray -> resolvedPath.any { schema.validate(it, OutputCollector.flag()).valid } + else -> schema.validate(resolvedPath, OutputCollector.flag()).valid + } } else true } } @@ -36,297 +43,14 @@ class JsonObjectEnquirer { object PresentationDefinitionParser { - fun matchCredentialsForInputDescriptor(credentials: List, inputDescriptor: PresentationDefinition.InputDescriptor): List { + fun matchCredentialsForInputDescriptor(credentials: List, inputDescriptor: PresentationDefinition.InputDescriptor): List { - println("--- Checking descriptor ${inputDescriptor.name} --") + log.debug { "--- Checking descriptor ${inputDescriptor.name} --" } val enquirer = JsonObjectEnquirer() - return enquirer.filterDocumentsByConstraints(credentials.map { it.toJsonObject() }, inputDescriptor.constraints.fields!!).map { W3CVC(it) } - - } - - fun matchCredentialsForDefinition(credentials: List, definition: PresentationDefinition) { + return enquirer.filterDocumentsByConstraints(credentials, inputDescriptor.constraints.fields!!) } } - - -fun main() { - //language=JSON - val credentials = """ - [ - { - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "https://business-standards.org/schemas/employment-history.json", - "type": [ - "VerifiableCredential", - "GenericEmploymentCredential" - ], - "issuer": "did:foo:123", - "issuanceDate": "2010-01-01T19:73:24Z", - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "active": true - }, - "proof": { - "type": "EcdsaSecp256k1VerificationKey2019", - "created": "2017-06-18T21:19:10Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "https://example.edu/issuers/keys/1", - "jws": "..." - } - }, - { - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "https://eu.com/claims/DriversLicense", - "type": [ - "EUDriversLicense" - ], - "issuer": "did:foo:123", - "issuanceDate": "2010-01-01T19:73:24Z", - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "license": { - "number": "34DGE352", - "dob": "07/13/80" - } - }, - "proof": { - "type": "RsaSignature2018", - "created": "2017-06-18T21:19:10Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "https://example.edu/issuers/keys/1", - "jws": "..." - } - } - ] - """.trimIndent() - - //language=JSON - val inputDescriptors = """ - [ - { - "id": "banking_input_1", - "name": "Bank Account Information", - "purpose": "Bank Account required to remit payment.", - "group": [ - "A" - ], - "constraints": { - "limit_disclosure": "required", - "fields": [ - { - "path": [ - "${'$'}.issuer", - "${'$'}.vc.issuer", - "${'$'}.iss" - ], - "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor or regulatory authority.", - "filter": { - "type": "string", - "pattern": "^did:example:123${'$'}|^did:example:456${'$'}" - } - }, - { - "path": [ - "${'$'}.credentialSubject.account[*].account_number", - "${'$'}.vc.credentialSubject.account[*].account_number", - "${'$'}.account[*].account_number" - ], - "purpose": "We can only remit payment to a currently-valid bank account in the US, France, or Germany, submitted as an ABA Acct # or IBAN.", - "filter": { - "type": "string", - "pattern": "^[0-9]{10-12}|^(DE|FR)[0-9]{2}\\s?([0-9a-zA-Z]{4}\\s?){4}[0-9a-zA-Z]{2}${'$'}" - }, - "intent_to_retain": true - }, - { - "path": [ - "${'$'}.credentialSubject.portfolio_value", - "${'$'}.vc.credentialSubject.portfolio_value", - "${'$'}.portfolio_value" - ], - "purpose": "A current portfolio value of at least one million dollars is required to insure your application", - "filter": { - "type": "number", - "minimum": 1000000 - }, - "intent_to_retain": true - } - ] - } - }, - { - "id": "banking_input_2", - "name": "Bank Account Information", - "purpose": "We can only remit payment to a currently-valid bank account.", - "group": [ - "A" - ], - "constraints": { - "fields": [ - { - "path": [ - "${'$'}.issuer", - "${'$'}.vc.issuer", - "${'$'}.iss" - ], - "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor or regulatory authority.", - "filter": { - "type": "string", - "pattern": "^did:example:123${'$'}|^did:example:456${'$'}" - } - }, - { - "path": [ - "${'$'}.credentialSubject.account[*].id", - "${'$'}.vc.credentialSubject.account[*].id", - "${'$'}.account[*].id" - ], - "purpose": "We can only remit payment to a currently-valid bank account in the US, France, or Germany, submitted as an ABA Acct # or IBAN.", - "filter": { - "type": "string", - "pattern": "^[0-9]{10-12}|^(DE|FR)[0-9]{2}\\s?([0-9a-zA-Z]{4}\\s?){4}[0-9a-zA-Z]{2}${'$'}" - }, - "intent_to_retain": true - }, - { - "path": [ - "${'$'}.credentialSubject.account[*].route", - "${'$'}.vc.credentialSubject.account[*].route", - "${'$'}.account[*].route" - ], - "purpose": "We can only remit payment to a currently-valid account at a US, Japanese, or German federally-accredited bank, submitted as an ABA RTN or SWIFT code.", - "filter": { - "type": "string", - "pattern": "^[0-9]{9}|^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?${'$'}" - }, - "intent_to_retain": true - } - ] - } - }, - { - "id": "employment_input", - "name": "Employment History", - "purpose": "We are only verifying one current employment relationship, not any other information about employment.", - "group": [ - "B" - ], - "constraints": { - "limit_disclosure": "required", - "fields": [ - { - "path": [ - "${'$'}.jobs[*].active" - ], - "filter": { - "type": "boolean", - "pattern": "true" - } - } - ] - } - }, - { - "id": "drivers_license_input_1", - "name": "EU Driver's License", - "group": [ - "C" - ], - "constraints": { - "fields": [ - { - "path": [ - "${'$'}.issuer", - "${'$'}.vc.issuer", - "${'$'}.iss" - ], - "purpose": "We can only accept digital driver's licenses issued by national authorities of EU member states or trusted notarial auditors.", - "filter": { - "type": "string", - "pattern": "did:example:gov1|did:example:gov2" - } - }, - { - "path": [ - "${'$'}.credentialSubject.dob", - "${'$'}.credentialSubject.license.dob", - "${'$'}.vc.credentialSubject.dob", - "${'$'}.dob" - ], - "purpose": "We must confirm that the driver was at least 21 years old on April 16, 2020.", - "filter": { - "type": "string", - "format": "date" - } - } - ] - } - }, - { - "id": "drivers_license_input_2", - "name": "Driver's License from one of 50 US States", - "group": [ - "C" - ], - "constraints": { - "fields": [ - { - "path": [ - "${'$'}.issuer", - "${'$'}.vc.issuer", - "${'$'}.iss" - ], - "purpose": "We can only accept digital driver's licenses issued by the 50 US states' automative affairs agencies.", - "filter": { - "type": "string", - "pattern": "did:example:gov1|did:web:dmv.ca.gov|did:example:oregonDMV" - } - }, - { - "path": [ - "${'$'}.credentialSubject.birth_date", - "${'$'}.vc.credentialSubject.birth_date", - "${'$'}.birth_date" - ], - "purpose": "We must confirm that the driver was at least 21 years old on April 16, 2020.", - "filter": { - "type": "string", - "format": "date", - "forrmatMaximum": "1999-05-16" - } - } - ] - } - }, - { - "id": "wa_driver_license", - "name": "Washington State Business License", - "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference", - "constraints": { - "fields": [ - { - "path": [ - "${'$'}.credentialSubject.dateOfBirth", - "${'$'}.credentialSubject.dob", - "${'$'}.vc.credentialSubject.dateOfBirth", - "${'$'}.vc.credentialSubject.dob" - ] - } - ] - } - } - ] - """.trimIndent() - - val credentialList = Json.decodeFromString>(credentials) - val inputDescriptorList = Json.decodeFromString>(inputDescriptors) - - inputDescriptorList.forEach { - val matched = PresentationDefinitionParser.matchCredentialsForInputDescriptor(credentialList, it) - println("Matched for ${it.name}: $matched") - } -} diff --git a/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationSubmission.kt b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationSubmission.kt index d8af07ba8..aec676a1d 100644 --- a/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationSubmission.kt +++ b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonMain/kotlin/id/walt/definitionparser/PresentationSubmission.kt @@ -16,7 +16,7 @@ data class PresentationSubmission( ) { @Serializable data class Descriptor( - val id: String, + val id: String? = null, val format: JsonElement, val path: String, diff --git a/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonTest/kotlin/id/walt/definitionparser/PresentationDefinitionParserTest.kt b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonTest/kotlin/id/walt/definitionparser/PresentationDefinitionParserTest.kt new file mode 100644 index 000000000..affa4a7d5 --- /dev/null +++ b/waltid-libraries/credentials/waltid-dif-definitions-parser/src/commonTest/kotlin/id/walt/definitionparser/PresentationDefinitionParserTest.kt @@ -0,0 +1,290 @@ +package id.walt.definitionparser + +import id.walt.credentials.vc.vcs.W3CVC +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.serialization.json.Json +import kotlin.test.Test + +class PresentationDefinitionParserTest { + + //language=JSON + val credentials = """ + [ + { + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "https://business-standards.org/schemas/employment-history.json", + "type": [ + "VerifiableCredential", + "GenericEmploymentCredential" + ], + "issuer": "did:foo:123", + "issuanceDate": "2010-01-01T19:73:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "active": true + }, + "proof": { + "type": "EcdsaSecp256k1VerificationKey2019", + "created": "2017-06-18T21:19:10Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://example.edu/issuers/keys/1", + "jws": "..." + } + }, + { + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "https://eu.com/claims/DriversLicense", + "type": [ + "EUDriversLicense" + ], + "issuer": "did:foo:123", + "issuanceDate": "2010-01-01T19:73:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "license": { + "number": "34DGE352", + "dob": "07/13/80" + } + }, + "proof": { + "type": "RsaSignature2018", + "created": "2017-06-18T21:19:10Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://example.edu/issuers/keys/1", + "jws": "..." + } + } + ] + """.trimIndent() + + //language=JSON + val inputDescriptors = """ + [ + { + "id": "banking_input_1", + "name": "Bank Account Information", + "purpose": "Bank Account required to remit payment.", + "group": [ + "A" + ], + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": [ + "${'$'}.issuer", + "${'$'}.vc.issuer", + "${'$'}.iss" + ], + "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor or regulatory authority.", + "filter": { + "type": "string", + "pattern": "^did:example:123${'$'}|^did:example:456${'$'}" + } + }, + { + "path": [ + "${'$'}.credentialSubject.account[*].account_number", + "${'$'}.vc.credentialSubject.account[*].account_number", + "${'$'}.account[*].account_number" + ], + "purpose": "We can only remit payment to a currently-valid bank account in the US, France, or Germany, submitted as an ABA Acct # or IBAN.", + "filter": { + "type": "string", + "pattern": "^[0-9]{10-12}|^(DE|FR)[0-9]{2}\\s?([0-9a-zA-Z]{4}\\s?){4}[0-9a-zA-Z]{2}${'$'}" + }, + "intent_to_retain": true + }, + { + "path": [ + "${'$'}.credentialSubject.portfolio_value", + "${'$'}.vc.credentialSubject.portfolio_value", + "${'$'}.portfolio_value" + ], + "purpose": "A current portfolio value of at least one million dollars is required to insure your application", + "filter": { + "type": "number", + "minimum": 1000000 + }, + "intent_to_retain": true + } + ] + } + }, + { + "id": "banking_input_2", + "name": "Bank Account Information", + "purpose": "We can only remit payment to a currently-valid bank account.", + "group": [ + "A" + ], + "constraints": { + "fields": [ + { + "path": [ + "${'$'}.issuer", + "${'$'}.vc.issuer", + "${'$'}.iss" + ], + "purpose": "We can only verify bank accounts if they are attested by a trusted bank, auditor or regulatory authority.", + "filter": { + "type": "string", + "pattern": "^did:example:123${'$'}|^did:example:456${'$'}" + } + }, + { + "path": [ + "${'$'}.credentialSubject.account[*].id", + "${'$'}.vc.credentialSubject.account[*].id", + "${'$'}.account[*].id" + ], + "purpose": "We can only remit payment to a currently-valid bank account in the US, France, or Germany, submitted as an ABA Acct # or IBAN.", + "filter": { + "type": "string", + "pattern": "^[0-9]{10-12}|^(DE|FR)[0-9]{2}\\s?([0-9a-zA-Z]{4}\\s?){4}[0-9a-zA-Z]{2}${'$'}" + }, + "intent_to_retain": true + }, + { + "path": [ + "${'$'}.credentialSubject.account[*].route", + "${'$'}.vc.credentialSubject.account[*].route", + "${'$'}.account[*].route" + ], + "purpose": "We can only remit payment to a currently-valid account at a US, Japanese, or German federally-accredited bank, submitted as an ABA RTN or SWIFT code.", + "filter": { + "type": "string", + "pattern": "^[0-9]{9}|^([a-zA-Z]){4}([a-zA-Z]){2}([0-9a-zA-Z]){2}([0-9a-zA-Z]{3})?${'$'}" + }, + "intent_to_retain": true + } + ] + } + }, + { + "id": "employment_input", + "name": "Employment History", + "purpose": "We are only verifying one current employment relationship, not any other information about employment.", + "group": [ + "B" + ], + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": [ + "${'$'}.jobs[*].active" + ], + "filter": { + "type": "boolean", + "pattern": "true" + } + } + ] + } + }, + { + "id": "drivers_license_input_1", + "name": "EU Driver's License", + "group": [ + "C" + ], + "constraints": { + "fields": [ + { + "path": [ + "${'$'}.issuer", + "${'$'}.vc.issuer", + "${'$'}.iss" + ], + "purpose": "We can only accept digital driver's licenses issued by national authorities of EU member states or trusted notarial auditors.", + "filter": { + "type": "string", + "pattern": "did:example:gov1|did:example:gov2" + } + }, + { + "path": [ + "${'$'}.credentialSubject.dob", + "${'$'}.credentialSubject.license.dob", + "${'$'}.vc.credentialSubject.dob", + "${'$'}.dob" + ], + "purpose": "We must confirm that the driver was at least 21 years old on April 16, 2020.", + "filter": { + "type": "string", + "format": "date" + } + } + ] + } + }, + { + "id": "drivers_license_input_2", + "name": "Driver's License from one of 50 US States", + "group": [ + "C" + ], + "constraints": { + "fields": [ + { + "path": [ + "${'$'}.issuer", + "${'$'}.vc.issuer", + "${'$'}.iss" + ], + "purpose": "We can only accept digital driver's licenses issued by the 50 US states' automative affairs agencies.", + "filter": { + "type": "string", + "pattern": "did:example:gov1|did:web:dmv.ca.gov|did:example:oregonDMV" + } + }, + { + "path": [ + "${'$'}.credentialSubject.birth_date", + "${'$'}.vc.credentialSubject.birth_date", + "${'$'}.birth_date" + ], + "purpose": "We must confirm that the driver was at least 21 years old on April 16, 2020.", + "filter": { + "type": "string", + "format": "date", + "forrmatMaximum": "1999-05-16" + } + } + ] + } + }, + { + "id": "wa_driver_license", + "name": "Washington State Business License", + "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference", + "constraints": { + "fields": [ + { + "path": [ + "${'$'}.credentialSubject.dateOfBirth", + "${'$'}.credentialSubject.dob", + "${'$'}.vc.credentialSubject.dateOfBirth", + "${'$'}.vc.credentialSubject.dob" + ] + } + ] + } + } + ] + """.trimIndent() + + private val log = KotlinLogging.logger { } + + //@Test + fun testPresentationDefinitionParser() { + val credentialList = Json.decodeFromString>(credentials) + val inputDescriptorList = Json.decodeFromString>(inputDescriptors) + + inputDescriptorList.forEach { + val matched = PresentationDefinitionParser.matchCredentialsForInputDescriptor(credentialList.map { it.toJsonObject() }, it) + log.debug { "Matched for ${it.name}: $matched" } + } + } +} diff --git a/waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts b/waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts index 6fdb20086..2cf1cf123 100644 --- a/waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts +++ b/waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts @@ -60,6 +60,7 @@ kotlin { implementation("io.github.optimumcode:json-schema-validator:0.2.2") implementation(project(":waltid-libraries:credentials:waltid-verifiable-credentials")) + implementation(project(":waltid-libraries:credentials:waltid-dif-definitions-parser")) implementation(project(":waltid-libraries:sdjwt:waltid-sdjwt")) // Kotlinx diff --git a/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/Exceptions.kt b/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/Exceptions.kt index eaf9bafc9..928ee48af 100644 --- a/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/Exceptions.kt +++ b/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/Exceptions.kt @@ -70,6 +70,7 @@ data class WebhookPolicyException( @SerialName("PresentationDefinitionException") class PresentationDefinitionException( val missingCredentialTypes: List, + val presentationDefinitionMatch: Boolean ) : id.walt.policies.SerializableRuntimeException() @JsExport diff --git a/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/PolicyManager.kt b/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/PolicyManager.kt index 65a96fcf6..82529ec0e 100644 --- a/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/PolicyManager.kt +++ b/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/PolicyManager.kt @@ -4,6 +4,7 @@ import id.walt.policies.policies.* import id.walt.policies.policies.vp.HolderBindingPolicy import id.walt.policies.policies.vp.MaximumCredentialsPolicy import id.walt.policies.policies.vp.MinimumCredentialsPolicy +import id.walt.policies.policies.vp.PresentationDefinitionPolicy import kotlin.js.ExperimentalJsExport import kotlin.js.JsExport @@ -47,7 +48,8 @@ object PolicyManager { MaximumCredentialsPolicy(), HolderBindingPolicy(), AllowedIssuerPolicy(), - RevocationPolicy() + RevocationPolicy(), + PresentationDefinitionPolicy() ) } diff --git a/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/policies/vp/PresentationDefinitionPolicy.kt b/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/policies/vp/PresentationDefinitionPolicy.kt new file mode 100644 index 000000000..1f93121fc --- /dev/null +++ b/waltid-libraries/credentials/waltid-verification-policies/src/commonMain/kotlin/id/walt/policies/policies/vp/PresentationDefinitionPolicy.kt @@ -0,0 +1,77 @@ +package id.walt.policies.policies.vp + +import id.walt.credentials.utils.VCFormat +import id.walt.crypto.utils.JsonUtils.toJsonElement +import id.walt.crypto.utils.JwsUtils.decodeJws +import id.walt.definitionparser.PresentationDefinition +import id.walt.definitionparser.PresentationDefinitionParser +import id.walt.definitionparser.PresentationSubmission +import id.walt.policies.CredentialWrapperValidatorPolicy +import id.walt.sdjwt.SDJwt +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.* + +private val log = KotlinLogging.logger { } + +@Serializable +class PresentationDefinitionPolicy : CredentialWrapperValidatorPolicy( +) { + + override val name = "presentation-definition" + override val description = + "Verifies that with an Verifiable Presentation at minimum the list of credentials `request_credentials` has been presented." + override val supportedVCFormats = setOf(VCFormat.jwt_vp, VCFormat.jwt_vp_json, VCFormat.ldp_vp) + + override suspend fun verify(data: JsonObject, args: Any?, context: Map): Result { + val presentationDefinition = context["presentationDefinition"]?.toJsonElement() + ?.let { Json.decodeFromJsonElement(it) } + ?: throw IllegalArgumentException("No presentationDefinition in context!") + val presentationSubmission = context["presentationSubmission"]?.toJsonElement() + ?.let { Json.decodeFromJsonElement(it) } + ?: throw IllegalArgumentException("No presentationSubmission in context!") + val format = presentationSubmission.descriptorMap.firstOrNull()?.format?.let { Json.decodeFromJsonElement(it) } + + val requestedTypes = presentationDefinition.primitiveVerificationGetTypeList() + + val presentedTypes = when(format) { + VCFormat.sd_jwt_vc -> listOf(data["vct"]!!.jsonPrimitive.content) + else -> data["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.mapNotNull { + it.jsonPrimitive.contentOrNull?.let { SDJwt.parse(it) }?.fullPayload + ?.get("vc")?.jsonObject?.get("type")?.jsonArray?.last()?.jsonPrimitive?.contentOrNull + } ?: emptyList() + } + + val presentationDefinitionMatch = when(format) { + VCFormat.sd_jwt_vc -> PresentationDefinitionParser.matchCredentialsForInputDescriptor( + listOf(data), presentationDefinition.inputDescriptors.first() + ).isNotEmpty() + else -> data["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.mapIndexedNotNull { idx,cred -> + val payload = cred.jsonPrimitive.contentOrNull?.let { SDJwt.parse(it) }?.fullPayload ?: throw IllegalArgumentException("Credential $idx is not a valid JWT string") + PresentationDefinitionParser.matchCredentialsForInputDescriptor( + listOf(payload.get("vc")?.jsonObject ?: throw IllegalArgumentException("Credential $idx has no vc property")), + presentationDefinition.inputDescriptors.get(idx) + ).isNotEmpty() + }!!.all { it } + } + + val success = presentedTypes.containsAll(requestedTypes) && presentationDefinitionMatch + + return if (success) + Result.success(presentedTypes) + else { + log.debug { "Requested types: $requestedTypes" } + log.debug { "Presented types: $presentedTypes" } + log.debug { "Presentation definition: $presentationDefinition" } + log.debug { "Presented data: $data" } + + Result.failure( + id.walt.policies.PresentationDefinitionException( + missingCredentialTypes = requestedTypes.minus( + presentedTypes.toSet() + ), presentationDefinitionMatch + ) + ) + } + } +} diff --git a/waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts b/waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts index 98d5422ba..38f2fc035 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts +++ b/waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts @@ -129,7 +129,6 @@ kotlin { } val jvmMain by getting { dependencies { - implementation("com.nfeld.jsonpathkt:jsonpathkt:2.0.1") implementation("io.ktor:ktor-client-okhttp:$ktor_version") } } diff --git a/waltid-libraries/waltid-library-commons/build.gradle.kts b/waltid-libraries/waltid-library-commons/build.gradle.kts index 2d6ff054e..7babeeb9a 100644 --- a/waltid-libraries/waltid-library-commons/build.gradle.kts +++ b/waltid-libraries/waltid-library-commons/build.gradle.kts @@ -88,8 +88,6 @@ kotlin { iosSimulatorArm64() } - val ktor_version = "2.3.12" - sourceSets { all { diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETest.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETest.kt index 0c832293c..ec7c33f4d 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETest.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETest.kt @@ -78,6 +78,7 @@ class E2ETest { } } + @OptIn(ExperimentalUuidApi::class) @Test fun e2e() = testBlock(defaultTestTimeout) { var client = testHttpClient() @@ -312,7 +313,7 @@ class E2ETest { lspPotentialIssuance.testTrack2() } - // @Test + //@Test fun lspVerifierTests() = testBlock(timeout = defaultTestTimeout) { val client = testHttpClient(doFollowRedirects = false) val lspPotentialVerification = LspPotentialVerification(client) @@ -320,6 +321,11 @@ class E2ETest { lspPotentialVerification.testPotentialInteropTrack4() } + //@Test + fun testExternalSignatureAPIs() = testBlock(defaultTestTimeout) { + ExchangeExternalSignatures().executeTestCases() + } + suspend fun setupTestWallet(): LspPotentialWallet { var client = testHttpClient() client.post("/wallet-api/auth/login") { diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETestWebService.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETestWebService.kt index 18d994fd3..721329c36 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETestWebService.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/E2ETestWebService.kt @@ -16,7 +16,6 @@ import id.walt.issuer.issuerModule import id.walt.webwallet.web.plugins.walletAuthenticationPluginAmendment import id.walt.issuer.lspPotential.lspPotentialIssuanceTestApi import id.walt.verifier.lspPotential.lspPotentialVerificationTestApi -import id.walt.verifier.policies.PresentationDefinitionPolicy import id.walt.verifier.verifierModule import id.walt.webwallet.db.Db import id.walt.webwallet.webWalletModule @@ -86,7 +85,6 @@ object E2ETestWebService { ), init = { webWalletSetup() - PolicyManager.registerPolicies(PresentationDefinitionPolicy()) WaltidServices.minimalInit() Db.start() }, diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeExternalSignaturesApi.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeExternalSignaturesApi.kt index 89d09bf15..85d184387 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeExternalSignaturesApi.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeExternalSignaturesApi.kt @@ -149,6 +149,9 @@ class ExchangeExternalSignatures { private val openbadgePresentationRequest = loadResource( "presentation/openbadgecredential-presentation-request.json" ) + private val openbadgeSdJwtPresentationRequest = loadResource( + "presentation/openbadgecredential-sd-presentation-request.json" + ) private val openbadgeUniversityDegreePresentationRequest = loadResource( "presentation/batch-openbadge-universitydegree-presentation-request.json" ) @@ -321,8 +324,8 @@ class ExchangeExternalSignatures { openbadgeSdJwtIssuanceRequest, ), ) - testOID4VP(openbadgePresentationRequest) - testOID4VP(openbadgePresentationRequest, true) + testOID4VP(openbadgeSdJwtPresentationRequest) + testOID4VP(openbadgeSdJwtPresentationRequest, true) clearWalletCredentials() } diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialVerification.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialVerification.kt index b361d0c75..507764b00 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialVerification.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialVerification.kt @@ -30,8 +30,7 @@ import id.walt.mdoc.doc.VerificationType import id.walt.mdoc.docrequest.MDocRequestBuilder import id.walt.mdoc.mdocauth.DeviceAuthentication import id.walt.oid4vc.OpenID4VP -import id.walt.oid4vc.data.dif.DescriptorMapping -import id.walt.oid4vc.data.dif.PresentationSubmission +import id.walt.oid4vc.data.dif.* import id.walt.oid4vc.interfaces.PresentationResult import id.walt.oid4vc.requests.AuthorizationRequest import id.walt.sdjwt.SDJwtVC @@ -58,6 +57,7 @@ import kotlin.uuid.Uuid class LspPotentialVerification(private val client: HttpClient) { + @OptIn(ExperimentalUuidApi::class) suspend fun testPotentialInteropTrack3() = E2ETestWebService.test("test track 3") { println("Starting test") @@ -185,8 +185,27 @@ class LspPotentialVerification(private val client: HttpClient) { contentType(ContentType.Application.Json) setBody( buildJsonObject { - put("request_credentials", JsonArray(listOf(RequestedCredential(format = VCFormat.sd_jwt_vc, vct = "identity_credential_vc+sd-jwt").let { Json.encodeToJsonElement(it) }))) - put("vp_policies", JsonArray(listOf(JsonPrimitive("signature_sd-jwt-vc")))) + put("request_credentials", JsonArray(listOf( + RequestedCredential( + format = VCFormat.sd_jwt_vc, + vct = "urn:eu.europa.ec.eudi:pid:1", + inputDescriptor = InputDescriptor( + id = "urn:eu.europa.ec.eudi:pid:1", + format = mapOf(VCFormat.sd_jwt_vc to VCFormatDefinition()), + constraints = InputDescriptorConstraints( + limitDisclosure = DisclosureLimitation.required, + fields = listOf( + InputDescriptorField(path = listOf("$.vct"), filter = JsonObject( + mapOf("type" to JsonPrimitive("string"), "pattern" to JsonPrimitive("urn:eu.europa.ec.eudi:pid:1"))) + ), + InputDescriptorField(path = listOf("$.birthdate"), filter = JsonObject( + mapOf("type" to JsonPrimitive("string"), "pattern" to JsonPrimitive(".*"))) + ) + ) + ) + ) + ).let { Json.encodeToJsonElement(it) }))) + put("vp_policies", JsonArray(listOf(JsonPrimitive("signature_sd-jwt-vc"), JsonPrimitive("presentation-definition")))) put("vc_policies", JsonArray(listOf(JsonPrimitive("not-before"), JsonPrimitive("expired"), JsonObject(mapOf( "policy" to JsonPrimitive("allowed-issuer"), @@ -200,7 +219,7 @@ class LspPotentialVerification(private val client: HttpClient) { assertNotNull(presReq.presentationDefinition) assertNotNull(presReq.responseUri) assertEquals(VCFormat.sd_jwt_vc, presReq.presentationDefinition!!.inputDescriptors.firstOrNull()?.format?.keys?.first()) - assertEquals("identity_credential_vc+sd-jwt", presReq.presentationDefinition!!.inputDescriptors.flatMap { it.constraints!!.fields!! }.first { it.path.contains("$.vct") }.filter?.get("pattern")?.jsonPrimitive?.content) + assertEquals("urn:eu.europa.ec.eudi:pid:1", presReq.presentationDefinition!!.inputDescriptors.flatMap { it.constraints!!.fields!! }.first { it.path.contains("$.vct") }.filter?.get("pattern")?.jsonPrimitive?.content) val ecHolderKey = ECKey.parse(holderKey.exportJWK()) val cryptoProvider = SimpleMultiKeyJWTCryptoProvider(mapOf( @@ -209,13 +228,19 @@ class LspPotentialVerification(private val client: HttpClient) { )) // 4. present (wallet) val vp_token = sdJwtVc.present(true, presReq.clientId, presReq.nonce!!, cryptoProvider, holderKey.getKeyId()).toString() + val vp_token_undisclosed = sdJwtVc.present(false, presReq.clientId, presReq.nonce!!, cryptoProvider, holderKey.getKeyId()).toString() println(vp_token) + println(vp_token_undisclosed) assertTrue(SDJwtVC.isSdJwtVCPresentation(vp_token)) + assertTrue(SDJwtVC.isSdJwtVCPresentation(vp_token_undisclosed)) val parseAndVerifyResult = SDJwtVC.parseAndVerify(vp_token, cryptoProvider, false, audience = presReq.clientId, nonce = presReq.nonce) + val parseAndVerifyUndisclosedResult = SDJwtVC.parseAndVerify(vp_token_undisclosed, cryptoProvider, false, audience = presReq.clientId, nonce = presReq.nonce) assertTrue(parseAndVerifyResult.verified) assertTrue(parseAndVerifyResult.sdJwtVC.toString().equals(vp_token)) + assertTrue(parseAndVerifyUndisclosedResult.verified) + assertTrue(parseAndVerifyUndisclosedResult.sdJwtVC.toString().equals(vp_token_undisclosed)) val tokenResp = OpenID4VP.generatePresentationResponse(PresentationResult( listOf(JsonPrimitive(vp_token)), @@ -224,9 +249,18 @@ class LspPotentialVerification(private val client: HttpClient) { )) )) println(tokenResp) + val tokenRespUndisclosed = OpenID4VP.generatePresentationResponse(PresentationResult( + listOf(JsonPrimitive(vp_token_undisclosed)), + PresentationSubmission("presentation_1", presReq.presentationDefinition!!.id, listOf( + DescriptorMapping(presReq.presentationDefinition!!.id, VCFormat.sd_jwt_vc, path = "$") + )) + )) + println(tokenRespUndisclosed) val httpResp = client.submitForm(presReq.responseUri!!, parametersOf(tokenResp.toHttpParameters())) + val httpRespUndisclosed = client.submitForm(presReq.responseUri!!, parametersOf(tokenRespUndisclosed.toHttpParameters())) assertEquals(200, httpResp.status.value) + assertEquals(400, httpRespUndisclosed.status.value) } } } diff --git a/waltid-services/waltid-e2e-tests/src/test/resources/presentation/openbadgecredential-presentation-request.json b/waltid-services/waltid-e2e-tests/src/test/resources/presentation/openbadgecredential-presentation-request.json index ed2577a9f..c17bf8776 100644 --- a/waltid-services/waltid-e2e-tests/src/test/resources/presentation/openbadgecredential-presentation-request.json +++ b/waltid-services/waltid-e2e-tests/src/test/resources/presentation/openbadgecredential-presentation-request.json @@ -2,5 +2,6 @@ "request_credentials": [ { "type": "OpenBadgeCredential", "format": "jwt_vc_json" } - ] + ], + "vp_policies": [ "signature","presentation-definition" ] } diff --git a/waltid-services/waltid-e2e-tests/src/test/resources/presentation/openbadgecredential-sd-presentation-request.json b/waltid-services/waltid-e2e-tests/src/test/resources/presentation/openbadgecredential-sd-presentation-request.json new file mode 100644 index 000000000..ed2577a9f --- /dev/null +++ b/waltid-services/waltid-e2e-tests/src/test/resources/presentation/openbadgecredential-sd-presentation-request.json @@ -0,0 +1,6 @@ +{ + "request_credentials": + [ + { "type": "OpenBadgeCredential", "format": "jwt_vc_json" } + ] +} diff --git a/waltid-services/waltid-issuer-api/Dockerfile b/waltid-services/waltid-issuer-api/Dockerfile index 2802b6ce5..2fabca878 100644 --- a/waltid-services/waltid-issuer-api/Dockerfile +++ b/waltid-services/waltid-issuer-api/Dockerfile @@ -5,6 +5,7 @@ COPY settings.gradle.kts build.gradle.kts gradle.properties gradlew /work/ COPY waltid-libraries/credentials/waltid-verifiable-credentials/build.gradle.kts /work/waltid-libraries/credentials/waltid-verifiable-credentials/ COPY waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts /work/waltid-libraries/credentials/waltid-verification-policies/ +COPY waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts /work/waltid-libraries/credentials/waltid-dif-definitions-parser/ COPY waltid-libraries/crypto/waltid-crypto/build.gradle.kts /work/waltid-libraries/crypto/waltid-crypto/ COPY waltid-libraries/waltid-did/build.gradle.kts /work/waltid-libraries/waltid-did/ COPY waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts /work/waltid-libraries/protocols/waltid-openid4vc/ @@ -20,6 +21,7 @@ RUN gradle build || return 0 COPY waltid-libraries/credentials/waltid-verifiable-credentials/. /work/waltid-libraries/credentials/waltid-verifiable-credentials COPY waltid-libraries/credentials/waltid-verification-policies/. /work/waltid-libraries/credentials/waltid-verification-policies +COPY waltid-libraries/credentials/waltid-dif-definitions-parser/. /work/waltid-libraries/credentials/waltid-dif-definitions-parser COPY waltid-libraries/crypto/waltid-crypto/. /work/waltid-libraries/crypto/waltid-crypto COPY waltid-libraries/waltid-did/. /work/waltid-libraries/waltid-did COPY waltid-libraries/protocols/waltid-openid4vc/. /work/waltid-libraries/protocols/waltid-openid4vc diff --git a/waltid-services/waltid-verifier-api/Dockerfile b/waltid-services/waltid-verifier-api/Dockerfile index 1b0b72d05..8ff80516b 100644 --- a/waltid-services/waltid-verifier-api/Dockerfile +++ b/waltid-services/waltid-verifier-api/Dockerfile @@ -5,6 +5,7 @@ COPY settings.gradle.kts build.gradle.kts gradle.properties gradlew /work/ COPY waltid-libraries/credentials/waltid-verifiable-credentials/build.gradle.kts /work/waltid-libraries/credentials/waltid-verifiable-credentials/ COPY waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts /work/waltid-libraries/credentials/waltid-verification-policies/ +COPY waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts /work/waltid-libraries/credentials/waltid-dif-definitions-parser/ COPY waltid-libraries/crypto/waltid-crypto/build.gradle.kts /work/waltid-libraries/crypto/waltid-crypto/ COPY waltid-libraries/waltid-did/build.gradle.kts /work/waltid-libraries/waltid-did/ COPY waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts /work/waltid-libraries/protocols/waltid-openid4vc/ @@ -20,6 +21,7 @@ RUN gradle build || return 0 COPY waltid-libraries/credentials/waltid-verifiable-credentials/. /work/waltid-libraries/credentials/waltid-verifiable-credentials COPY waltid-libraries/credentials/waltid-verification-policies/. /work/waltid-libraries/credentials/waltid-verification-policies +COPY waltid-libraries/credentials/waltid-dif-definitions-parser/. /work/waltid-libraries/credentials/waltid-dif-definitions-parser COPY waltid-libraries/crypto/waltid-crypto/. /work/waltid-libraries/crypto/waltid-crypto COPY waltid-libraries/waltid-did/. /work/waltid-libraries/waltid-did COPY waltid-libraries/protocols/waltid-openid4vc/. /work/waltid-libraries/protocols/waltid-openid4vc diff --git a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/Main.kt b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/Main.kt index d4e8f7296..e4fe6e181 100644 --- a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/Main.kt +++ b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/Main.kt @@ -8,9 +8,9 @@ import id.walt.commons.web.WebService import id.walt.policies.PolicyManager import id.walt.did.dids.DidService import id.walt.did.dids.resolver.LocalResolver +import id.walt.policies.policies.vp.PresentationDefinitionPolicy import id.walt.verifier.entra.entraVerifierApi import id.walt.verifier.lspPotential.lspPotentialVerificationTestApi -import id.walt.verifier.policies.PresentationDefinitionPolicy import id.walt.verifier.web.plugins.configureHTTP import id.walt.verifier.web.plugins.configureMonitoring import id.walt.verifier.web.plugins.configureRouting @@ -26,8 +26,6 @@ suspend fun main(args: Array) { registerResolver(LocalResolver()) updateResolversForMethods() } - - PolicyManager.registerPolicies(PresentationDefinitionPolicy()) }, run = WebService(Application::verifierModule).run() ) diff --git a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt index 95709b0a6..5fee20060 100644 --- a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt +++ b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApi.kt @@ -191,6 +191,7 @@ fun Application.verfierApi() { example("Example with EBSI PDA1 Presentation Definition", VerifierApiExamples.EbsiVerifiablePDA1) example("MDoc verification example", VerifierApiExamples.lspPotentialMdocExample) example("SD-JWT-VC verification example", VerifierApiExamples.lspPotentialSDJwtVCExample) + example("SD-JWT-VC verification example with mandatory fields", VerifierApiExamples.sdJwtVCExampleWithRequiredFields) } } diff --git a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApiExamples.kt b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApiExamples.kt index 0febb73c5..972100150 100644 --- a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApiExamples.kt +++ b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/VerifierApiExamples.kt @@ -281,4 +281,10 @@ object VerifierApiExamples { """.trimIndent() ) + + val sdJwtVCExampleWithRequiredFields = jsonObjectValueExampleDescriptorDsl( + """ + {"request_credentials":[{"format":"vc+sd-jwt","vct":"https://issuer.portal.walt-test.cloud/identity_credential","input_descriptor":{"id":"https://issuer.portal.walt-test.cloud/identity_credential","format":{"vc+sd-jwt":{}},"constraints":{"fields":[{"path":["${'$'}.vct"],"filter":{"type":"string","pattern":"https://issuer.portal.walt-test.cloud/identity_credential"}},{"path":["${'$'}.birthdate"],"filter":{"type":"string","pattern":".*"}}],"limit_disclosure":"required"}}}],"vp_policies":["signature_sd-jwt-vc","presentation-definition"],"vc_policies":["not-before","expired"]} + """.trimIndent() + ) } diff --git a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/OIDCVerifierService.kt b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/OIDCVerifierService.kt index cf8189125..73da957d7 100644 --- a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/OIDCVerifierService.kt +++ b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/oidc/OIDCVerifierService.kt @@ -42,10 +42,10 @@ import id.walt.oid4vc.providers.OpenIDCredentialVerifier import id.walt.oid4vc.providers.PresentationSession import id.walt.oid4vc.responses.TokenResponse import id.walt.oid4vc.util.randomUUID +import id.walt.policies.policies.vp.PresentationDefinitionPolicy import id.walt.sdjwt.SDJwtVC import id.walt.sdjwt.WaltIdJWTCryptoProvider import id.walt.verifier.config.OIDCVerifierServiceConfig -import id.walt.verifier.policies.PresentationDefinitionPolicy import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.server.plugins.* import kotlinx.coroutines.runBlocking @@ -190,12 +190,13 @@ object OIDCVerifierService : OpenIDCredentialVerifier( vpPolicies = policies.vpPolicies, globalVcPolicies = policies.vcPolicies, specificCredentialPolicies = policies.specificPolicies, - presentationContext = mapOf( - "presentationDefinition" to session.presentationDefinition, + presentationContext = listOfNotNull( + "presentationDefinition" to session.presentationDefinition.toJSON(), + tokenResponse.presentationSubmission?.toJSON()?.let { "presentationSubmission" to it }, "challenge" to (session.authorizationRequest?.nonce ?: ""), "clientId" to (session.authorizationRequest?.clientId ?: ""), "responseUri" to (session.authorizationRequest?.responseUri ?: "") - ) + ).toMap() ) } diff --git a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/policies/PresentationDefinitionPolicy.kt b/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/policies/PresentationDefinitionPolicy.kt deleted file mode 100644 index b6be85a2c..000000000 --- a/waltid-services/waltid-verifier-api/src/main/kotlin/id/walt/verifier/policies/PresentationDefinitionPolicy.kt +++ /dev/null @@ -1,51 +0,0 @@ -package id.walt.verifier.policies - -import id.walt.credentials.utils.VCFormat -import id.walt.crypto.utils.JwsUtils.decodeJws -import id.walt.oid4vc.data.dif.PresentationDefinition -import id.walt.policies.CredentialWrapperValidatorPolicy -import io.github.oshai.kotlinlogging.KotlinLogging -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.* - -private val log = KotlinLogging.logger { } - -@Serializable -class PresentationDefinitionPolicy : CredentialWrapperValidatorPolicy( -) { - - override val name = "presentation-definition" - override val description = - "Verifies that with an Verifiable Presentation at minimum the list of credentials `request_credentials` has been presented." - override val supportedVCFormats = setOf(VCFormat.jwt_vp, VCFormat.jwt_vp_json, VCFormat.ldp_vp) - - override suspend fun verify(data: JsonObject, args: Any?, context: Map): Result { - val presentationDefinition = context["presentationDefinition"] as? PresentationDefinition - ?: throw IllegalArgumentException("No presentationDefinition in context!") - - val requestedTypes = presentationDefinition.primitiveVerificationGetTypeList() - - val presentedTypes = - data["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.mapNotNull { - it.jsonPrimitive.contentOrNull?.decodeJws()?.payload - ?.jsonObject?.get("vc")?.jsonObject?.get("type")?.jsonArray?.last()?.jsonPrimitive?.contentOrNull - } ?: emptyList() - - val success = presentedTypes.containsAll(requestedTypes) - - return if (success) - Result.success(presentedTypes) - else { - log.debug { "Requested types: $requestedTypes" } - log.debug { "Presented types: $presentedTypes" } - - Result.failure( - id.walt.policies.PresentationDefinitionException( - missingCredentialTypes = requestedTypes.minus( - presentedTypes.toSet() - ) - ) - ) - } - } -} diff --git a/waltid-services/waltid-wallet-api/Dockerfile b/waltid-services/waltid-wallet-api/Dockerfile index 79b7bd0cc..f04ded5d4 100644 --- a/waltid-services/waltid-wallet-api/Dockerfile +++ b/waltid-services/waltid-wallet-api/Dockerfile @@ -11,6 +11,7 @@ COPY waltid-libraries/waltid-did/build.gradle.kts /work/waltid-libraries/waltid- COPY waltid-libraries/credentials/waltid-mdoc-credentials/build.gradle.kts /work/waltid-libraries/credentials/waltid-mdoc-credentials/ COPY waltid-libraries/credentials/waltid-verifiable-credentials/build.gradle.kts /work/waltid-libraries/credentials/waltid-verifiable-credentials/ COPY waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts /work/waltid-libraries/credentials/waltid-verification-policies/ +COPY waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts /work/waltid-libraries/credentials/waltid-dif-definitions-parser/ COPY waltid-libraries/auth/waltid-ktor-authnz/build.gradle.kts /work/waltid-libraries/auth/waltid-ktor-authnz/ COPY waltid-libraries/auth/waltid-permissions/build.gradle.kts /work/waltid-libraries/auth/waltid-permissions/ COPY waltid-libraries/waltid-library-commons/build.gradle.kts /work/waltid-libraries/waltid-library-commons/ @@ -29,6 +30,7 @@ COPY waltid-libraries/waltid-did/. /work/waltid-libraries/waltid-did COPY waltid-libraries/credentials/waltid-mdoc-credentials/. /work/waltid-libraries/credentials/waltid-mdoc-credentials COPY waltid-libraries/credentials/waltid-verifiable-credentials/. /work/waltid-libraries/credentials/waltid-verifiable-credentials COPY waltid-libraries/credentials/waltid-verification-policies/. /work/waltid-libraries/credentials/waltid-verification-policies +COPY waltid-libraries/credentials/waltid-dif-definitions-parser/. /work/waltid-libraries/credentials/waltid-dif-definitions-parser COPY waltid-libraries/auth/waltid-ktor-authnz/. /work/waltid-libraries/auth/waltid-ktor-authnz COPY waltid-libraries/auth/waltid-permissions/. /work/waltid-libraries/auth/waltid-permissions COPY waltid-libraries/waltid-library-commons/. /work/waltid-libraries/waltid-library-commons