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

openid4vp alpha #2172

Draft
wants to merge 32 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e992c27
fix: inital all tests running
auer-martin Jan 27, 2025
49310ec
Merge branch 'main' of github.com:openwallet-foundation/credo-ts
auer-martin Jan 27, 2025
bedc0df
Merge branch 'main' of github.com:openwallet-foundation/credo-ts
auer-martin Jan 28, 2025
d380e2c
fix: running with some fixes
auer-martin Jan 28, 2025
e6be043
feat: transaction data alpha
auer-martin Jan 30, 2025
ac175e3
feat: openid4vp alpha
auer-martin Feb 3, 2025
35f5118
Merge branch 'main' of github.com:openwallet-foundation/credo-ts
auer-martin Feb 3, 2025
84b0ec1
feat: dcql
auer-martin Feb 4, 2025
bfb1362
fix: incorporate feedback
auer-martin Feb 10, 2025
eefd66d
feat: web-origin partial
auer-martin Feb 17, 2025
099ecba
fix: allow dcql_query to be object
auer-martin Feb 17, 2025
91b944d
feat: remove siop specific handling
auer-martin Feb 18, 2025
fb7ec79
feat: merge with main and dc api fixes (#3)
TimoGlastra Feb 19, 2025
6faa4b4
fix: origin handling
TimoGlastra Feb 22, 2025
b171d6a
fix client id value
TimoGlastra Feb 23, 2025
3686c05
fix: sd-jwt fixes
TimoGlastra Feb 23, 2025
682dfb9
fix: several fixes for dcql/pex/session transcript
TimoGlastra Feb 23, 2025
4f745d5
fix: submission is optional
TimoGlastra Feb 23, 2025
f435a0a
fix: credential ordering. Only support dc+sd-jwt for now
TimoGlastra Feb 23, 2025
02fa671
feat: intial support for dc api verification
TimoGlastra Feb 24, 2025
9b2a530
remove unused const
TimoGlastra Feb 24, 2025
975dc44
update to released alpha
TimoGlastra Feb 24, 2025
f2f3b1e
docs(changeset): allow A128GCM as allowed value for ECDH encryption
TimoGlastra Feb 24, 2025
800e4c0
cleanup
TimoGlastra Feb 24, 2025
4762d20
better dc api support
TimoGlastra Feb 25, 2025
5e32d3e
update
TimoGlastra Feb 25, 2025
e9cc905
style
TimoGlastra Feb 25, 2025
8c4291e
update to latest credo
TimoGlastra Feb 25, 2025
db30e4f
Merge remote-tracking branch 'upstream/main' into openid4vp-alpha
TimoGlastra Feb 25, 2025
54f42ef
fix payload
TimoGlastra Feb 26, 2025
7945815
dc api signed
TimoGlastra Feb 26, 2025
86f7dde
fix find session
TimoGlastra Feb 26, 2025
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
5 changes: 5 additions & 0 deletions .changeset/hungry-singers-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/openid4vc': minor
---

feat(openid4vc): openid4vp alpha
5 changes: 5 additions & 0 deletions .changeset/nice-laws-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/openid4vc': minor
---

feat(openid4vc): add support for new dcql query syntax for oid4vp
6 changes: 6 additions & 0 deletions .changeset/nice-poems-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@credo-ts/askar': patch
'@credo-ts/core': patch
---

allow A128GCM as allowed value for ECDH encryption
5 changes: 5 additions & 0 deletions .changeset/tame-stringrays-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/core': patch
---

feat: add `claimFormat` to `Mdoc`, `MdocDeviceResponse` and `SdJwtVc` to allow for easier type narrowing
35 changes: 22 additions & 13 deletions demo-openid/src/Holder.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { OpenId4VciResolvedCredentialOffer, OpenId4VcSiopResolvedAuthorizationRequest } from '@credo-ts/openid4vc'
import type {
OpenId4VciMetadata,
OpenId4VciResolvedCredentialOffer,
OpenId4VcSiopResolvedAuthorizationRequest,
} from '@credo-ts/openid4vc'

import { AskarModule } from '@credo-ts/askar'
import {
W3cJwtVerifiableCredential,
W3cJsonLdVerifiableCredential,
DifPresentationExchangeService,
Mdoc,
DidKey,
DidJwk,
Expand Down Expand Up @@ -64,7 +67,7 @@ export class Holder extends BaseAgent<ReturnType<typeof getOpenIdHolderModules>>
return await this.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer)
}

public async resolveIssuerMetadata(credentialIssuer: string) {
public async resolveIssuerMetadata(credentialIssuer: string): Promise<OpenId4VciMetadata> {
return await this.agent.modules.openId4VcHolder.resolveIssuerMetadata(credentialIssuer)
}

Expand Down Expand Up @@ -192,21 +195,27 @@ export class Holder extends BaseAgent<ReturnType<typeof getOpenIdHolderModules>>
}

public async acceptPresentationRequest(resolvedPresentationRequest: OpenId4VcSiopResolvedAuthorizationRequest) {
const presentationExchangeService = this.agent.dependencyManager.resolve(DifPresentationExchangeService)

if (!resolvedPresentationRequest.presentationExchange) {
throw new Error('Missing presentation exchange on resolved authorization request')
if (!resolvedPresentationRequest.presentationExchange && !resolvedPresentationRequest.dcql) {
throw new Error('Missing presentation exchange or dcql on resolved authorization request')
}

const submissionResult = await this.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({
authorizationRequest: resolvedPresentationRequest.authorizationRequest,
presentationExchange: {
credentials: presentationExchangeService.selectCredentialsForRequest(
resolvedPresentationRequest.presentationExchange.credentialsForRequest
),
},
presentationExchange: resolvedPresentationRequest.presentationExchange
? {
credentials: this.agent.modules.openId4VcHolder.selectCredentialsForPresentationExchangeRequest(
resolvedPresentationRequest.presentationExchange.credentialsForRequest
),
}
: undefined,
dcql: resolvedPresentationRequest.dcql
? {
credentials: this.agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(
resolvedPresentationRequest.dcql.queryResult
),
}
: undefined,
})

return submissionResult.serverResponse
}

Expand Down
61 changes: 42 additions & 19 deletions demo-openid/src/HolderInquirer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
OpenId4VciResolvedCredentialOffer,
} from '@credo-ts/openid4vc'

import { DifPresentationExchangeService, Mdoc } from '@credo-ts/core'
import { Mdoc } from '@credo-ts/core'
import { preAuthorizedCodeGrantIdentifier } from '@credo-ts/openid4vc'
import console, { clear } from 'console'
import { textSync } from 'figlet'
Expand Down Expand Up @@ -217,24 +217,47 @@ export class HolderInquirer extends BaseInquirer {
const proofRequestUri = await this.inquireInput('Enter proof request: ')
this.resolvedPresentationRequest = await this.holder.resolveProofRequest(proofRequestUri)

const presentationDefinition = this.resolvedPresentationRequest?.presentationExchange?.definition
console.log(greenText(`Presentation Purpose: '${presentationDefinition?.purpose}'`))

if (this.resolvedPresentationRequest?.presentationExchange?.credentialsForRequest.areRequirementsSatisfied) {
const selectedCredentials = Object.values(
this.holder.agent.dependencyManager
.resolve(DifPresentationExchangeService)
.selectCredentialsForRequest(this.resolvedPresentationRequest.presentationExchange.credentialsForRequest)
).flatMap((e) => e)
if (this.resolvedPresentationRequest.presentationExchange) {
const presentationDefinition = this.resolvedPresentationRequest.presentationExchange.definition
console.log(
greenText(
`All requirements for creating the presentation are satisfied. The following credentials will be shared`,
true
)
greenText(`Received DIF Presentation Exchange request with purpose: '${presentationDefinition.purpose}'`)
)
selectedCredentials.forEach(this.printCredential)
} else {
console.log(redText(`No credentials available that satisfy the proof request.`))

if (this.resolvedPresentationRequest.presentationExchange.credentialsForRequest.areRequirementsSatisfied) {
const selectedCredentials = Object.values(
this.holder.agent.modules.openId4VcHolder.selectCredentialsForPresentationExchangeRequest(
this.resolvedPresentationRequest.presentationExchange.credentialsForRequest
)
).flatMap((e) => e)
console.log(
greenText(
`All requirements for creating the presentation are satisfied. The following credentials will be shared`,
true
)
)
selectedCredentials.forEach(this.printCredential)
} else {
console.log(redText(`No credentials available that satisfy the proof request.`))
}
} else if (this.resolvedPresentationRequest.dcql) {
console.log(greenText('Received DCQL request'))

if (this.resolvedPresentationRequest.dcql.queryResult.canBeSatisfied) {
const selectedCredentials = Object.values(
this.holder.agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(
this.resolvedPresentationRequest.dcql.queryResult
)
).flatMap((e) => e.credentialRecord)
console.log(
greenText(
`All requirements for creating the presentation are satisfied. The following credentials will be shared`,
true
)
)
selectedCredentials.forEach(this.printCredential)
} else {
console.log(redText(`No credentials available that satisfy the proof request.`))
}
}
}

Expand All @@ -245,10 +268,10 @@ export class HolderInquirer extends BaseInquirer {

const serverResponse = await this.holder.acceptPresentationRequest(this.resolvedPresentationRequest)

if (serverResponse.status >= 200 && serverResponse.status < 300) {
if (serverResponse && serverResponse.status >= 200 && serverResponse.status < 300) {
console.log(`received success status code '${serverResponse.status}'`)
} else {
console.log(`received error status code '${serverResponse.status}'. ${JSON.stringify(serverResponse.body)}`)
console.log(`received error status code '${serverResponse?.status}'. ${JSON.stringify(serverResponse?.body)}`)
}

this.resolvedPresentationRequest = undefined
Expand Down
116 changes: 109 additions & 7 deletions demo-openid/src/Verifier.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'
import type { DcqlQuery, DifPresentationExchangeDefinitionV2 } from '@credo-ts/core'
import type { OpenId4VcVerifierRecord } from '@credo-ts/openid4vc'

import { AskarModule } from '@credo-ts/askar'
Expand All @@ -11,8 +11,92 @@ import { Output } from './OutputClass'

const VERIFIER_HOST = process.env.VERIFIER_HOST ?? 'http://localhost:4000'

const universityDegreeDcql = {
credential_sets: [
{
required: true,
options: [
['UniversityDegreeCredential-vc+sd-jwt'],
['UniversityDegreeCredential-jwt_vc_json-ld'],
['UniversityDegreeCredential-jwt_vc_json'],
],
},
],
credentials: [
{
id: 'UniversityDegreeCredential-vc+sd-jwt',
format: 'vc+sd-jwt',
meta: {
vct_values: ['UniversityDegree'],
},
},
{
id: 'UniversityDegreeCredential-jwt_vc_json-ld',
format: 'jwt_vc_json-ld',
claims: [
{
path: ['vc', 'type'],
values: ['UniversityDegree'],
},
],
},
{
id: 'UniversityDegreeCredential-jwt_vc_json',
format: 'jwt_vc_json',
claims: [
{
path: ['vc', 'type'],
values: ['UniversityDegree'],
},
],
},
],
} satisfies DcqlQuery

const openBadgeCredentialDcql = {
credential_sets: [
{
required: true,
options: [
['OpenBadgeCredential-vc+sd-jwt'],
['OpenBadgeCredential-jwt_vc_json-ld'],
['OpenBadgeCredential-jwt_vc_json'],
],
},
],
credentials: [
{
id: 'OpenBadgeCredential-vc+sd-jwt',
format: 'vc+sd-jwt',
meta: {
vct_values: ['OpenBadgeCredential'],
},
},
{
id: 'OpenBadgeCredential-jwt_vc_json-ld',
format: 'jwt_vc_json-ld',
claims: [
{
path: ['vc', 'type'],
values: ['OpenBadgeCredential'],
},
],
},
{
id: 'OpenBadgeCredential-jwt_vc_json',
format: 'jwt_vc_json',
claims: [
{
path: ['vc', 'type'],
values: ['OpenBadgeCredential'],
},
],
},
],
} satisfies DcqlQuery

const universityDegreePresentationDefinition = {
id: 'UniversityDegreeCredential',
id: 'UniversityDegreeCredential - DIF Presentation Exchange',
purpose: 'Present your UniversityDegreeCredential to verify your education level.',
input_descriptors: [
{
Expand All @@ -34,7 +118,7 @@ const universityDegreePresentationDefinition = {
}

const openBadgeCredentialPresentationDefinition = {
id: 'OpenBadgeCredential',
id: 'OpenBadgeCredential - DIF Presentation Exchange',
purpose: 'Provide proof of employment to confirm your employment status.',
input_descriptors: [
{
Expand All @@ -55,6 +139,11 @@ const openBadgeCredentialPresentationDefinition = {
],
}

export const dcqls = [
{ id: 'UniversityDegreeCredential - DCQL', dcql: universityDegreeDcql },
{ id: 'OpenBadgeCredential - DCQL', dcql: openBadgeCredentialDcql },
]

export const presentationDefinitions = [
universityDegreePresentationDefinition,
openBadgeCredentialPresentationDefinition,
Expand Down Expand Up @@ -90,16 +179,29 @@ export class Verifier extends BaseAgent<{ askar: AskarModule; openId4VcVerifier:
}

// TODO: add method to show the received presentation submission
public async createProofRequest(presentationDefinition: DifPresentationExchangeDefinitionV2) {
public async createProofRequest({
presentationDefinition,
dcql,
}: {
presentationDefinition?: DifPresentationExchangeDefinitionV2
dcql?: DcqlQuery
}) {
const { authorizationRequest } = await this.agent.modules.openId4VcVerifier.createAuthorizationRequest({
requestSigner: {
method: 'did',
didUrl: this.verificationMethod.id,
},
verifierId: this.verifierRecord.verifierId,
presentationExchange: {
definition: presentationDefinition,
},
presentationExchange: presentationDefinition
? {
definition: presentationDefinition,
}
: undefined,
dcql: dcql
? {
query: dcql,
}
: undefined,
})

return authorizationRequest
Expand Down
12 changes: 8 additions & 4 deletions demo-openid/src/VerifierInquirer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { textSync } from 'figlet'

import { BaseInquirer } from './BaseInquirer'
import { Title, purpleText } from './OutputClass'
import { Verifier, presentationDefinitions } from './Verifier'
import { Verifier, presentationDefinitions, dcqls } from './Verifier'

export const runVerifier = async () => {
clear()
Expand Down Expand Up @@ -49,11 +49,15 @@ export class VerifierInquirer extends BaseInquirer {
}

public async createProofRequest() {
const presentationDefinitionId = await this.pickOne(presentationDefinitions.map((p) => p.id))
const presentationDefinitionId = await this.pickOne([
...presentationDefinitions.map((p) => p.id),
...dcqls.map((d) => d.id),
])
const presentationDefinition = presentationDefinitions.find((p) => p.id === presentationDefinitionId)
if (!presentationDefinition) throw new Error('No presentation definition found')
const dcql = dcqls.find((dcql) => dcql.id === presentationDefinitionId)?.dcql
if (!presentationDefinition && !dcql) throw new Error('No presentation definition, or dcql query found')

const proofRequest = await this.verifier.createProofRequest(presentationDefinition)
const proofRequest = await this.verifier.createProofRequest({ presentationDefinition, dcql })

console.log(purpleText(`Proof request for the presentation of an ${presentationDefinitionId}.\n'${proofRequest}'`))
}
Expand Down
Loading
Loading