Skip to content

Commit

Permalink
Merge pull request #174 from Sphereon-Opensource/feature/SPRIND-137
Browse files Browse the repository at this point in the history
feature/SPRIND-137
  • Loading branch information
sanderPostma authored Jan 23, 2025
2 parents c159817 + 44db119 commit 49de36e
Show file tree
Hide file tree
Showing 41 changed files with 1,014 additions and 496 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-test-on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
node-version: '20.x'
- uses: pnpm/action-setup@v4
with:
version: 9
version: 9.15.3 # TODO remove later, we are temporary dealing with a broken pnpm version in the CI container
- run: pnpm install
- run: pnpm build
- name: run CI tests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-test-publish-on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
node-version: '20.x'
- uses: pnpm/action-setup@v4
with:
version: 9
version: 9.15.3 # TODO remove later, we are temporary dealing with a broken pnpm version in the CI container
- run: pnpm install
- run: pnpm build
- name: run integration tests
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"node": ">=18"
},
"resolutions": {
"@sphereon/ssi-types": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.32.1-feature.VDX.341.53",
"dcql": "0.2.19",
"node-fetch": "2.6.12"
},
"prettier": {
Expand Down Expand Up @@ -66,4 +67,3 @@
"OID4VP"
]
}

2 changes: 1 addition & 1 deletion packages/callback-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@sphereon/oid4vci-client": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/oid4vci-issuer": "workspace:*",
"@sphereon/ssi-types": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.32.1-feature.VDX.341.53",
"jose": "^4.10.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"dependencies": {
"@sphereon/oid4vc-common": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/ssi-types": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.32.1-feature.VDX.341.53",
"cross-fetch": "^3.1.8",
"debug": "^4.3.5"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"build:clean": "tsc --build --clean && tsc --build"
},
"dependencies": {
"@sphereon/ssi-types": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.32.1-feature.VDX.341.53",
"jwt-decode": "^4.0.0",
"sha.js": "^2.4.11",
"uint8arrays": "3.1.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/issuer-rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/oid4vci-issuer": "workspace:*",
"@sphereon/ssi-express-support": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.32.1-feature.VDX.341.53",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/issuer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@sphereon/oid4vc-common": "workspace:*",
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/ssi-types": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.32.1-feature.VDX.341.53",
"uuid": "^9.0.0"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/oid4vci-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"dependencies": {
"@sphereon/oid4vc-common": "workspace:*",
"@sphereon/ssi-types": "0.30.2-feature.mdoc.funke2.367",
"@sphereon/ssi-types": "0.32.1-feature.VDX.341.53",
"cross-fetch": "^3.1.8",
"debug": "^4.3.5",
"jwt-decode": "^4.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
VERIFIERZ_PURPOSE_TO_VERIFY,
VERIFIERZ_PURPOSE_TO_VERIFY_NL,
} from './data/mockedData'
import { DcqlQuery } from 'dcql'

const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'
const EXAMPLE_REFERENCE_URL = 'https://rp.acme.com/siop/jwts'
Expand Down Expand Up @@ -671,4 +672,85 @@ describe('create Request JWT should', () => {
}
await expect(URI.fromOpts(opts)).rejects.toThrow(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID)
})

it('should succeed when requesting with a valid dcql query', async () => {
const dcqlQuery: DcqlQuery = {
credentials: [
{
id: 'Credentials',
format: 'jwt_vc_json',
claims: [
{
id: 'ID Card Credential',
path: ['$.issuer.id'],
values: ['did:example:issuer'],
},
],
},
],
}
const opts: CreateAuthorizationRequestOpts = {
version: SupportedVersion.SIOPv2_ID1,
payload: {
client_id: WELL_KNOWN_OPENID_FEDERATION,
scope: 'test',
response_type: 'vp_token',
request_object_signing_alg_values_supported: [SigningAlgo.ES256, SigningAlgo.EDDSA],
redirect_uri: EXAMPLE_REDIRECT_URL,
},
requestObject: {
jwtIssuer: { method: 'did', didUrl: KID, alg: SigningAlgo.ES256K },
passBy: PassBy.REFERENCE,
reference_uri: EXAMPLE_REFERENCE_URL,

createJwtCallback: getCreateJwtCallback({
hexPrivateKey: HEX_KEY,
did: DID,
kid: KID,
alg: SigningAlgo.ES256K,
}),
payload: {
client_id: WELL_KNOWN_OPENID_FEDERATION,
scope: 'test',
response_type: 'vp_token',
redirect_uri: EXAMPLE_REDIRECT_URL,
request_object_signing_alg_values_supported: [SigningAlgo.EDDSA, SigningAlgo.ES256],
claims: {
vp_token: {
dcql_query: JSON.stringify(dcqlQuery)
},
},
},
},
clientMetadata: {
client_id: WELL_KNOWN_OPENID_FEDERATION,
idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256],
requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256],
responseTypesSupported: [ResponseType.ID_TOKEN],
scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID],
subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID],
subjectTypesSupported: [SubjectType.PAIRWISE],
vpFormatsSupported: {
ldp_vc: {
proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019],
},
},

passBy: PassBy.VALUE,

logo_uri: VERIFIER_LOGO_FOR_CLIENT,
clientName: VERIFIER_NAME_FOR_CLIENT,
'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100305',
clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY,
'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL,
},
}

const uriRequest = await URI.fromOpts(opts)

const uriDecoded = decodeURIComponent(uriRequest.encodedUri)
expect(uriDecoded.startsWith('openid4vp://?')).toBeTruthy()
expect(uriDecoded).toContain(`request_uri=https://rp.acme.com/siop/jwts`)
expect((await (await uriRequest.toAuthorizationRequest())?.requestObject?.getPayload())?.claims.vp_token).toBeDefined()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IVerifiablePresentation,
OriginalVerifiableCredential,
} from '@sphereon/ssi-types'
import { DcqlCredential, DcqlPresentation, DcqlQuery, DcqlQueryResult } from 'dcql'

import {
AuthorizationResponse,
Expand All @@ -27,12 +28,12 @@ import {
VerifyAuthorizationRequestOpts,
VPTokenLocation,
} from '..'
import { createPresentationSubmission } from '../authorization-response/OpenID4VP'
import { createPresentationSubmission } from '../authorization-response'
import SIOPErrors from '../types/Errors'

import { getCreateJwtCallback, getVerifyJwtCallback } from './DidJwtTestUtils'
import { getResolver } from './ResolverTestUtils'
import { mockedGetEnterpriseAuthToken, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'
import { mockedGetEnterpriseAuthToken, pexHasher, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils'
import {
UNIT_TEST_TIMEOUT,
VERIFIER_LOGO_FOR_CLIENT,
Expand Down Expand Up @@ -90,6 +91,7 @@ describe('create JWT from Request JWT should', () => {

const resolver = getResolver('ethr')
const verifyOpts: VerifyAuthorizationRequestOpts = {
hasher: pexHasher,
verifyJwtCallback: getVerifyJwtCallback(resolver),
verification: {},
supportedVersions: [SupportedVersion.SIOPv2_ID1],
Expand Down Expand Up @@ -463,9 +465,6 @@ describe('create JWT from Request JWT should', () => {
}

const requestObject = await RequestObject.fromOpts(requestOpts)
/* console.log(
JSON.stringify(await AuthenticationResponse.createJWTFromRequestJWT(requestWithJWT.jwt, responseOpts, verifyOpts))
);*/
const jwt = await requestObject.toJwt()
if (!jwt) throw new Error('JWT is undefined')
const authorizationRequest = await AuthorizationResponse.fromRequestObject(jwt, responseOpts, verifyOpts)
Expand Down Expand Up @@ -650,4 +649,140 @@ describe('create JWT from Request JWT should', () => {
const authResponse = AuthorizationResponse.fromRequestObject(jwt, responseOpts, verifyOpts)
await expect(authResponse).toBeDefined()
})

it('succeed when valid JWT with DCQL query is passed in', async () => {
expect.assertions(1)

const mockReqEntity = await mockedGetEnterpriseAuthToken('REQ COMPANY')
const mockResEntity = await mockedGetEnterpriseAuthToken('RES COMPANY')

const dcqlQuery: DcqlQuery = {
credentials: [
{
id: 'Credentials',
format: 'vc+sd-jwt',
claims: [
{
path: ['given_name'],
values: ['John'],
},
],
},
],
}

const dcqlParsedQuery = DcqlQuery.parse(dcqlQuery)
DcqlQuery.validate(dcqlParsedQuery)

const requestOpts: CreateAuthorizationRequestOpts = {
version: SupportedVersion.SIOPv2_D12_OID4VP_D20,
requestObject: {
passBy: PassBy.REFERENCE,
reference_uri: 'https://my-request.com/here',
jwtIssuer: { method: 'did', didUrl: mockReqEntity.did, alg: SigningAlgo.ES256K },
createJwtCallback: getCreateJwtCallback({
hexPrivateKey: mockReqEntity.hexPrivateKey,
did: mockReqEntity.did,
kid: `${mockReqEntity.did}#controller`,
alg: SigningAlgo.ES256K,
}),
payload: {
client_id: WELL_KNOWN_OPENID_FEDERATION,
scope: 'test',
response_type: 'id_token vp_token',
redirect_uri: EXAMPLE_REDIRECT_URL,
dcql_query: JSON.stringify(dcqlParsedQuery),
},
},
clientMetadata: {
client_id: WELL_KNOWN_OPENID_FEDERATION,
idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256],
subject_syntax_types_supported: ['did:ethr:', SubjectIdentifierType.DID],
requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256],
responseTypesSupported: [ResponseType.ID_TOKEN],
scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID],
subjectTypesSupported: [SubjectType.PAIRWISE],
vpFormatsSupported: {
ldp_vc: {
proof_type: [IProofType.EcdsaSecp256k1Signature2019, IProofType.EcdsaSecp256k1Signature2019],
},
},
passBy: PassBy.VALUE,
logo_uri: VERIFIER_LOGO_FOR_CLIENT,
clientName: VERIFIER_NAME_FOR_CLIENT,
'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100315',
clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY,
'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL,
},
}

const sdjwt = {
compactJwtVc:
'eyJ0eXAiOiJ2YytzZC1qd3QiLCJraWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW5WelpTSTZJbk5wWnlJc0ltdDBlU0k2SWtWRElpd2lZM0oySWpvaVVDMHlOVFlpTENKNElqb2lTMGRwYzNodlUzaDJhVzB4YTFOSU1XSnROMnhmUkhCeVIyczNZa2RrWkVaYVdXWnRjVXB1VjJWb1NTSXNJbmtpT2lKYVEzQldUVVZSTkhsNGNUSlZiVGRDVGpoSVQyNUdlamszTTFBMFVUQlVkbmRuZVhWUlgyRmlURlZWSW4wIzAiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW5WelpTSTZJbk5wWnlJc0ltdDBlU0k2SWtWRElpd2lZM0oySWpvaVVDMHlOVFlpTENKNElqb2lXRkpXUVhsNVJIQldNbXBNTm5oNlUwSktZM1JIZW0xS1pqbFFlV0Z4WHpNdFRVeHJlR0ZoUlRBNFRTSXNJbmtpT2lKQ1NtOUtWM05WYTBaQlUyVlRZMmx4VDFsNVNWTTBZMFpoZVU4emFHaEJTalZaYjJ0dU9IcFRTVEZuSW4wIzAiLCJpc3MiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW5WelpTSTZJbk5wWnlJc0ltdDBlU0k2SWtWRElpd2lZM0oySWpvaVVDMHlOVFlpTENKNElqb2lTMGRwYzNodlUzaDJhVzB4YTFOSU1XSnROMnhmUkhCeVIyczNZa2RrWkVaYVdXWnRjVXB1VjJWb1NTSXNJbmtpT2lKYVEzQldUVVZSTkhsNGNUSlZiVGRDVGpoSVQyNUdlamszTTFBMFVUQlVkbmRuZVhWUlgyRmlURlZWSW4wIzAiLCJpYXQiOjE3MzQ0NTgwNDIsInZjdCI6InVybjpldS5ldXJvcGEuZWMuZXVkaTpwaWQ6MSIsIl9zZCI6WyIwWWdpR25hck1LSERnVWx1QktteGdRZUJ4OF8xazMwd3NSRVN1X2t1Y0JFIiwiNEpwU2JzUEsxZndUQzRhR24zZ0hXb1BkVEJrMExOTWZMOEJXeEIxZ1JPWSIsIjRpZmplQUZveUEyQmc0STVNWEFrMVlyc1AtUVBQQXRnZGlHY0RMQTZiTFEiLCJBdTFjdURISUNISE1jUjZxM2R3d1pHbHp5dzMwSXhvTGVhWlNxRktEbmo4IiwiQ0QxbWxOY19VaVBoQUp6YkJveTVMY3dtekFNZTM3d0VLZF9iMTB6QTNxNCIsInFwZ1FOUVRac0VJWHdxUk9fT24xdUVCSVVNODBTcTJLR2tlN0JSU2N0WHciXSwiX3NkX2FsZyI6IlNIQS0yNTYifQ.P84d0CoS4M-zQ29l3S97RMatfJMYkoTgR5EqSMTdYlZAMp4e8iiuz2PXQMfJ-_undCvg4SRXxDACGiLL3Tt7Bw~WyJlNTFiNWI2NS0wNzM3LTQ0MjQtYTUxYS1jNGYzZGNlZGFmMmYiLCJnaXZlbl9uYW1lIiwiSm9obiJd~WyIxM2I1NDIwNi1kYWQ3LTQ3N2UtODYyZC03N2ZiMTQ1MDE5NjUiLCJmYW1pbHlfbmFtZSIsIkRvZSJd~WyJkMmQxNjg3Zi04ZmY4LTRlOTMtYWJjYi1hYTNlNGVjYzY0ZTMiLCJlbWFpbCIsImpvaG5kZW9AZXhhbXBsZS5jb20iXQ~WyIyZDA4YTk2YS03YzYwLTQ3NDEtYTI5YS00ZjBjYTFlNGQ3M2IiLCJwaG9uZSIsIisxLTIwMi01NTUtMDEwMSJd~WyI2YjVkN2FmOS01ZmIxLTQzNTEtYWM1ZS1hMzA1YTBkNjU0ZDUiLCJhZGRyZXNzIix7InN0cmVldF9hZGRyZXNzIjoiMTIzIE1haW4gU3QiLCJsb2NhbGl0eSI6IkFueXRvd24iLCJyZWdpb24iOiJBbnlzdGF0ZSIsImNvdW50cnkiOiJVUyJ9XQ~WyI5MmYzY2M5ZC0yMjQ2LTRiODQtYTk5OS0xYmQyM2U0OGQ0MGEiLCJiaXJ0aGRhdGUiLCIxOTQwLTAxLTAxIl0~',
decodedPayload: {
header: {
typ: 'vc+sd-jwt',
kid: 'did:jwk:eyJhbGciOiJFUzI1NiIsInVzZSI6InNpZyIsImt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiS0dpc3hvU3h2aW0xa1NIMWJtN2xfRHByR2s3YkdkZEZaWWZtcUpuV2VoSSIsInkiOiJaQ3BWTUVRNHl4cTJVbTdCTjhIT25Gejk3M1A0UTBUdndneXVRX2FiTFVVIn0#0',
alg: 'ES256',
},
payload: {
sub: 'did:jwk:eyJhbGciOiJFUzI1NiIsInVzZSI6InNpZyIsImt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiWFJWQXl5RHBWMmpMNnh6U0JKY3RHem1KZjlQeWFxXzMtTUxreGFhRTA4TSIsInkiOiJCSm9KV3NVa0ZBU2VTY2lxT1l5SVM0Y0ZheU8zaGhBSjVZb2tuOHpTSTFnIn0#0',
iss: 'did:jwk:eyJhbGciOiJFUzI1NiIsInVzZSI6InNpZyIsImt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiS0dpc3hvU3h2aW0xa1NIMWJtN2xfRHByR2s3YkdkZEZaWWZtcUpuV2VoSSIsInkiOiJaQ3BWTUVRNHl4cTJVbTdCTjhIT25Gejk3M1A0UTBUdndneXVRX2FiTFVVIn0#0',
iat: 1734458042,
vct: 'urn:eu.europa.ec.eudi:pid:1',
given_name: 'John',
email: 'johndeo@example.com',
birthdate: '1940-01-01',
phone: '+1-202-555-0101',
address: {
street_address: '123 Main St',
locality: 'Anytown',
region: 'Anystate',
country: 'US',
},
family_name: 'Doe',
},
kb: undefined,
},
}

const vc: DcqlCredential = {
credential_format: 'vc+sd-jwt',
vct: sdjwt.decodedPayload.payload.vct,
claims: sdjwt.decodedPayload.payload,
}

const dcqlQueryResult: DcqlQueryResult = DcqlQuery.query(dcqlQuery, [vc])

const presentation: DcqlPresentation.Output = {}
for (const [key, value] of Object.entries(dcqlQueryResult.credential_matches)) {
if (value.success) {
presentation[key] = sdjwt.compactJwtVc
}
}

const dcqlPresentation = DcqlPresentation.parse(presentation)

const responseOpts: AuthorizationResponseOpts = {
responseURI: EXAMPLE_REDIRECT_URL,
responseURIType: 'redirect_uri',
createJwtCallback: getCreateJwtCallback({
did: mockResEntity.did,
hexPrivateKey: mockResEntity.hexPrivateKey,
kid: `${mockResEntity.did}#controller`,
alg: SigningAlgo.ES256K,
}),
jwtIssuer: { method: 'did', didUrl: `${mockResEntity.did}#controller`, alg: SigningAlgo.ES256K },
dcqlResponse: {
dcqlPresentation
},
responseMode: ResponseMode.DIRECT_POST,
}

const requestObject = await RequestObject.fromOpts(requestOpts)
const jwt = await requestObject.toJwt()
if (!jwt) throw new Error('JWT is undefined')
const authorizationRequest = await AuthorizationResponse.fromRequestObject(jwt, responseOpts, verifyOpts)
expect(authorizationRequest).toBeDefined()
})
})
2 changes: 1 addition & 1 deletion packages/siop-oid4vp/lib/__tests__/IT.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ describe.skip('RP and OP interaction should', () => {
const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt)
expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did)
await expect(op.createAuthorizationResponse(verifiedAuthReqWithJWT, {})).rejects.toThrow(
Error('authentication request expects a verifiable presentation in the response'),
Error('vp_token is present, but no presentation definitions or dcql query provided'),
)

expect(verifiedAuthReqWithJWT.payload?.['registration'].client_name).toEqual(VERIFIER_NAME_FOR_CLIENT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,7 @@ describe('presentation exchange manager tests', () => {
const payload = await getPayloadVID1Val()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(payload.claims?.vp_token as any).presentation_definition_uri = EXAMPLE_PD_URL
await expect(PresentationExchange.findValidPresentationDefinitions(payload)).rejects.toThrow(
SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE,
)
await expect(PresentationExchange.findValidPresentationDefinitions(payload)).rejects.toThrow(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE)
})

it('validatePresentationAgainstDefinition: should pass if provided VP match the PD', async function () {
Expand Down
Loading

0 comments on commit 49de36e

Please sign in to comment.