UNPKG

@sphereon/did-auth-siop

Version:

Self Issued OpenID V2 (SIOPv2) and OpenID 4 Verifiable Presentations (OID4VP)

554 lines (510 loc) 25.6 kB
import { EventEmitter } from 'events' import { defaultHasher, SigningAlgo } from '@sphereon/oid4vc-common' import { IPresentationDefinition } from '@sphereon/pex' import { decodeSdJwtVc, OriginalVerifiableCredential } from '@sphereon/ssi-types' import { DcqlCredential, DcqlQuery } from 'dcql' import { InMemoryRPSessionManager, OP, PassBy, PresentationDefinitionWithLocation, PresentationExchange, PresentationVerificationCallback, PropertyTarget, ResponseIss, ResponseMode, ResponseType, RevocationVerification, RP, Scope, SubjectType, SupportedVersion, VPTokenLocation, } from '../' import { getVerifyJwtCallback, internalSignature } from './DidJwtTestUtils' import { getResolver } from './ResolverTestUtils' import { mockedGetEnterpriseAuthToken, pexHasher, sdJwtVcPresentationSignCallback, WELL_KNOWN_OPENID_FEDERATION } from './TestUtils' import { VERIFIER_LOGO_FOR_CLIENT, VERIFIER_NAME_FOR_CLIENT, VERIFIER_NAME_FOR_CLIENT_NL, VERIFIERZ_PURPOSE_TO_VERIFY, VERIFIERZ_PURPOSE_TO_VERIFY_NL, } from './data/mockedData' jest.setTimeout(30000) type Json = | string | number | boolean | null | { // Not exported from dcql [key: string]: Json } | Json[] const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello' const HOLDER_DID = 'did:example:ebfeb1f712ebc6f1c276e12ec21' const SD_JWT_VC = 'eyJhbGciOiJFZERTQSIsInR5cCI6InZjK3NkLWp3dCJ9.eyJpYXQiOjE3MDA0NjQ3MzYwNzYsImlzcyI6ImRpZDprZXk6c29tZS1yYW5kb20tZGlkLWtleSIsIm5iZiI6MTcwMDQ2NDczNjE3NiwidmN0IjoiaHR0cHM6Ly9oaWdoLWFzc3VyYW5jZS5jb20vU3RhdGVCdXNpbmVzc0xpY2Vuc2UiLCJ1c2VyIjp7Il9zZCI6WyI5QmhOVDVsSG5QVmpqQUp3TnR0NDIzM216MFVVMUd3RmFmLWVNWkFQV0JNIiwiSVl5d1FQZl8tNE9hY2Z2S2l1cjRlSnFMa1ZleWRxcnQ1Y2UwMGJReWNNZyIsIlNoZWM2TUNLakIxeHlCVl91QUtvLURlS3ZvQllYbUdBd2VGTWFsd05xbUEiLCJXTXpiR3BZYmhZMkdoNU9pWTRHc2hRU1dQREtSeGVPZndaNEhaQW5YS1RZIiwiajZ6ZFg1OUJYZHlTNFFaTGJITWJ0MzJpenRzWXdkZzRjNkpzWUxNc3ZaMCIsInhKR3Radm41cFM4VEhqVFlJZ3MwS1N5VC1uR3BSR3hDVnp6c1ZEbmMyWkUiXX0sImxpY2Vuc2UiOnsibnVtYmVyIjoxMH0sImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VHZW1jIiwieSI6Ilp4amlXV2JaTVFHSFZXS1ZRNGhiU0lpcnNWZnVlY0NFNnQ0alQ5RjJIWlEifX0sIl9zZF9hbGciOiJzaGEtMjU2IiwiX3NkIjpbIl90YnpMeHBaeDBQVHVzV2hPOHRUZlVYU2ZzQjVlLUtrbzl3dmZaaFJrYVkiLCJ1WmNQaHdUTmN4LXpNQU1zemlYMkFfOXlJTGpQSEhobDhEd2pvVXJLVVdZIl19.HAcudVInhNpXkTPQGNosjKTFRJWgKj90NpfloRaDQchGd4zxc1ChWTCCPXzUXTBypASKrzgjZCiXlTr0bzmLAg~WyJHeDZHRUZvR2t6WUpWLVNRMWlDREdBIiwiZGF0ZU9mQmlydGgiLCIyMDAwMDEwMSJd~WyJ1LUt3cmJvMkZfTExQekdSZE1XLUtBIiwibmFtZSIsIkpvaG4iXQ~WyJNV1ZieGJqVFZxUXdLS3h2UGVZdWlnIiwibGFzdE5hbWUiLCJEb2UiXQ~' const KB_SD_JWT_PRESENTATION = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6a2V5OnNvbWUtcmFuZG9tLWRpZC1rZXkiLCJpYXQiOjE3MzU4MzY0NzY1NjksInZjdCI6Imh0dHBzOi8vaGlnaC1hc3N1cmFuY2UuY29tL1N0YXRlQnVzaW5lc3NMaWNlbnNlIiwiX3NkIjpbIk5ub3U2OGN6VG9qQWY0Z3dIMmJiNFBaWHB4WHJzQ29oWm5CbEZvQ293TTAiLCJ1ajZZZVZwSnRwUjhVWHRpbmVDOGM5LXpTRFJVQzJzSGhsRkNNNWtkLXlNIl0sIl9zZF9hbGciOiJzaGEtMjU2In0.X1pNBBmR-7h6SgxtmZY9GL_nSBzoIvWNw7nqqgJVCnSEbvyTjqTgu4bLSPKeaxf1jY2zHJK1jdxiDzIizRZ0gA~WyI4OTE4YjkxZGFjMzk1OTdkIiwidXNlciIseyJkYXRlX29mX2JpcnRoIjoiMDEvMDEvMTk3MCIsIl9zZCI6WyJ6UE8zb1RCT3BxMmRNcWQtekt5SmF2UlQyWVIwSHBPaV9jczZRajEtVmR3Il19XQ~WyIyOTc4NTNiODE5MTI0MTJkIiwibmFtZSIsIkpvaG4iXQ~WyJkYjEzNDQ4NTgxMzY0M2JlIiwibGljZW5zZSIseyJfc2QiOlsiWUFUR3A3TGxjNEMtTWtYWkZWTEF6RHRtbDFTMVpFWFFyTW5CdmVDWVFwayJdfV0~WyJlOTc3MzhiNmM0OGNhMWJlIiwibnVtYmVyIiwxMF0~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE3MzU4MzY0NzYsImF1ZCI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJub25jZSI6InFCclI3bXFuWTNRcjQ5ZEFaeWNQRjhGemdFODNtNkgwYzJsMGJ6UDR4U2ciLCJjdXN0b20iOiJkYXRhIiwic2RfaGFzaCI6IlVwYzNYQWpzRU1mdnVmSzJ5Q3RkNDZXUFJmTDVfVDc4UThEZVNZQXlEX28ifQ.Crw56nLFFnVulRQElpq9HoskdKIyd5Mj6vg9UVNSWfhxQ0oGe10RHtifUv4BiFharSvWN99y_DnkhCPu1sPIYw' function getPresentationDefinition(): IPresentationDefinition { return { id: '32f54163-7166-48f1-93d8-ff217bdb0653', name: 'Conference Entry Requirements', purpose: 'We can only allow people associated with Washington State business representatives into conference areas', format: { 'vc+sd-jwt': {}, }, input_descriptors: [ { 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: { limit_disclosure: 'required', fields: [ { path: ['$.vct'], filter: { type: 'string', const: 'https://high-assurance.com/StateBusinessLicense', }, }, { path: ['$.license.number'], filter: { type: 'number', }, }, { path: ['$.user.name'], filter: { type: 'string', }, }, ], }, }, ], } } const sdJwtVcQuery: DcqlQuery = { credentials: [ { id: 'my_credential', format: 'vc+sd-jwt', meta: { vct_values: ['https://high-assurance.com/StateBusinessLicense'], }, claims: [{ path: ['license', 'number'] }, { path: ['user', 'name'] }], }, ], } DcqlQuery.validate(sdJwtVcQuery) function getVCs(): OriginalVerifiableCredential[] { return [SD_JWT_VC] } describe.skip('RP and OP interaction should', () => { // FIXME SDK-45 Uniresolver failing it('succeed when calling with presentation definitions and right verifiable presentation', async () => { const opMock = await mockedGetEnterpriseAuthToken('OP') const opMockEntity = { ...opMock, didKey: `${opMock.did}#controller`, } const rpMock = await mockedGetEnterpriseAuthToken('RP') const rpMockEntity = { ...rpMock, didKey: `${rpMock.did}#controller`, } // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => { return { verified: true } } const resolver = getResolver('ethr') const eventEmitter = new EventEmitter() const replayRegistry = new InMemoryRPSessionManager(eventEmitter) const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_ID1 }) .withEventEmitter(eventEmitter) .withSessionManager(replayRegistry) .withClientId(rpMockEntity.did) .withScope('test') .withHasher(pexHasher) .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) .withPresentationVerification(presentationVerificationCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.VALUE) .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], responseTypesSupported: [ResponseType.ID_TOKEN], vpFormatsSupported: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], subjectTypesSupported: [SubjectType.PAIRWISE], subject_syntax_types_supported: ['did', 'did:key'], passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT, clientName: VERIFIER_NAME_FOR_CLIENT, 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100322', clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) .build() const op = OP.builder() .withPresentationSignCallback(sdJwtVcPresentationSignCallback) .withExpiresIn(1000) .withHasher(pexHasher) .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K)) .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], issuer: ResponseIss.SELF_ISSUED_V2, requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], responseTypesSupported: [ResponseType.ID_TOKEN, ResponseType.VP_TOKEN], vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], subjectTypesSupported: [SubjectType.PAIRWISE], subject_syntax_types_supported: [], passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT, clientName: VERIFIER_NAME_FOR_CLIENT, 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100323', clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) .build() const requestURI = await rp.createAuthorizationRequestURI({ correlationId: '1234', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', state: 'b32f0087fc9816eb813fd11f', }) // Let's test the parsing const parsedAuthReqURI = await op.parseAuthorizationRequestURI(requestURI.encodedUri) expect(parsedAuthReqURI.authorizationRequestPayload).toBeDefined() expect(parsedAuthReqURI.requestObjectJwt).toBeDefined() if (!parsedAuthReqURI.requestObjectJwt) throw new Error('requestObjectJwt is undefined') const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt) expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did) const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs(), hasher: pexHasher, }) const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( parsedAuthReqURI.authorizationRequestPayload, ) await pex.selectVerifiableCredentialsForSubmission(pd[0].definition) const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, getVCs(), sdJwtVcPresentationSignCallback, { proofOptions: { nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', }, }) const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT, { presentationExchange: { verifiablePresentations: verifiablePresentationResult.verifiablePresentations, vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, presentationSubmission: verifiablePresentationResult.presentationSubmission, }, }) expect(authenticationResponseWithJWT.response.payload).toBeDefined() expect(authenticationResponseWithJWT.response.idToken).toBeDefined() const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], }) expect(verifiedAuthResponseWithJWT.idToken?.jwt).toBeDefined() expect(verifiedAuthResponseWithJWT.idToken?.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg') }) it('succeed when calling with presentation definitions and right verifiable presentation without id token', async () => { const opMockEntity = await mockedGetEnterpriseAuthToken('OP') const rpMockEntity = await mockedGetEnterpriseAuthToken('RP') // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => { return { verified: true } } const resolver = getResolver('ethr') const eventEmitter = new EventEmitter() const replayRegistry = new InMemoryRPSessionManager(eventEmitter) const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_D12_OID4VP_D18, }) .withEventEmitter(eventEmitter) .withSessionManager(replayRegistry) .withClientId(rpMockEntity.did) .withHasher(pexHasher) .withResponseType([ResponseType.VP_TOKEN]) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withPresentationDefinition({ definition: getPresentationDefinition() }, [PropertyTarget.REQUEST_OBJECT, PropertyTarget.AUTHORIZATION_REQUEST]) .withPresentationVerification(presentationVerificationCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.VALUE) .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K)) .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], responseTypesSupported: [ResponseType.VP_TOKEN], vpFormatsSupported: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, subjectTypesSupported: [SubjectType.PAIRWISE], subject_syntax_types_supported: ['did', 'did:key'], passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT, clientName: VERIFIER_NAME_FOR_CLIENT, 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100322', clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) .build() const op = OP.builder() .withPresentationSignCallback(sdJwtVcPresentationSignCallback) .withExpiresIn(1000) .withHasher(pexHasher) .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K)) .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], issuer: ResponseIss.SELF_ISSUED_V2, requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], responseTypesSupported: [ResponseType.ID_TOKEN, ResponseType.VP_TOKEN], vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], subjectTypesSupported: [SubjectType.PAIRWISE], subject_syntax_types_supported: [], passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT, clientName: VERIFIER_NAME_FOR_CLIENT, 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100323', clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) .withSupportedVersions(SupportedVersion.SIOPv2_ID1) .build() const requestURI = await rp.createAuthorizationRequestURI({ correlationId: '1234', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', state: 'b32f0087fc9816eb813fd11f', jwtIssuer: { method: 'did', alg: SigningAlgo.ES256K, didUrl: `${rpMockEntity.did}#controller` }, }) // Let's test the parsing const parsedAuthReqURI = await op.parseAuthorizationRequestURI(requestURI.encodedUri) expect(parsedAuthReqURI.authorizationRequestPayload).toBeDefined() expect(parsedAuthReqURI.requestObjectJwt).toBeDefined() if (!parsedAuthReqURI.requestObjectJwt) throw new Error('requestObjectJwt is undefined') const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt) expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did) const pex = new PresentationExchange({ allDIDs: [HOLDER_DID], allVerifiableCredentials: getVCs(), hasher: pexHasher, }) const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( parsedAuthReqURI.authorizationRequestPayload, ) await pex.selectVerifiableCredentialsForSubmission(pd[0].definition) const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, getVCs(), sdJwtVcPresentationSignCallback, { proofOptions: { nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', }, }) const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT, { jwtIssuer: { method: 'did', alg: SigningAlgo.ES256K, didUrl: `${rpMockEntity.did}#controller`, }, presentationExchange: { verifiablePresentations: verifiablePresentationResult.verifiablePresentations, vpTokenLocation: VPTokenLocation.AUTHORIZATION_RESPONSE, presentationSubmission: verifiablePresentationResult.presentationSubmission, }, }) expect(authenticationResponseWithJWT.response.payload).toBeDefined() expect(authenticationResponseWithJWT.response.idToken).toBeUndefined() const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { presentationDefinitions: [{ definition: pd[0].definition, location: pd[0].location }], }) expect(verifiedAuthResponseWithJWT.oid4vpSubmission?.nonce).toEqual('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg') expect(verifiedAuthResponseWithJWT.idToken).toBeUndefined() }) it('succeed when calling with DCQL and right verifiable presentation', async () => { const opMock = await mockedGetEnterpriseAuthToken('OP') const opMockEntity = { ...opMock, didKey: `${opMock.did}#controller`, } const rpMock = await mockedGetEnterpriseAuthToken('RP') const rpMockEntity = { ...rpMock, didKey: `${rpMock.did}#controller`, } // eslint-disable-next-line @typescript-eslint/no-unused-vars const presentationVerificationCallback: PresentationVerificationCallback = async (_args) => { return { verified: true } } const resolver = getResolver('ethr') const eventEmitter = new EventEmitter() const replayRegistry = new InMemoryRPSessionManager(eventEmitter) const rp = RP.builder({ requestVersion: SupportedVersion.SIOPv2_D12_OID4VP_D20 }) .withEventEmitter(eventEmitter) .withSessionManager(replayRegistry) .withClientId(rpMockEntity.did) .withScope('test') .withHasher(pexHasher) .withResponseType([ResponseType.ID_TOKEN, ResponseType.VP_TOKEN]) .withResponseMode(ResponseMode.DIRECT_POST) .withRedirectUri(EXAMPLE_REDIRECT_URL) .withDcqlQuery(sdJwtVcQuery, [PropertyTarget.REQUEST_OBJECT]) .withPresentationVerification(presentationVerificationCallback) .withRevocationVerification(RevocationVerification.NEVER) .withRequestBy(PassBy.VALUE) .withCreateJwtCallback(internalSignature(rpMockEntity.hexPrivateKey, rpMockEntity.did, `${rpMockEntity.did}#controller`, SigningAlgo.ES256K)) .withAuthorizationEndpoint('www.myauthorizationendpoint.com') .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withClientMetadata({ client_id: WELL_KNOWN_OPENID_FEDERATION, idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], responseTypesSupported: [ResponseType.ID_TOKEN], vpFormatsSupported: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], subjectTypesSupported: [SubjectType.PAIRWISE], subject_syntax_types_supported: ['did', 'did:key'], passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT, clientName: VERIFIER_NAME_FOR_CLIENT, 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100322', clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) .build() const op = OP.builder() .withPresentationSignCallback(sdJwtVcPresentationSignCallback) .withExpiresIn(1000) .withHasher(pexHasher) .withCreateJwtCallback(internalSignature(opMockEntity.hexPrivateKey, opMockEntity.did, `${opMockEntity.did}#controller`, SigningAlgo.ES256K)) .withVerifyJwtCallback(getVerifyJwtCallback(resolver)) .withRegistration({ authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA], issuer: ResponseIss.SELF_ISSUED_V2, requestObjectSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], responseTypesSupported: [ResponseType.ID_TOKEN, ResponseType.VP_TOKEN], vpFormats: { jwt_vc: { alg: [SigningAlgo.EDDSA] } }, scopesSupported: [Scope.OPENID_DIDAUTHN, Scope.OPENID], subjectTypesSupported: [SubjectType.PAIRWISE], subject_syntax_types_supported: [], passBy: PassBy.VALUE, logo_uri: VERIFIER_LOGO_FOR_CLIENT, clientName: VERIFIER_NAME_FOR_CLIENT, 'clientName#nl-NL': VERIFIER_NAME_FOR_CLIENT_NL + '2022100323', clientPurpose: VERIFIERZ_PURPOSE_TO_VERIFY, 'clientPurpose#nl-NL': VERIFIERZ_PURPOSE_TO_VERIFY_NL, }) .withSupportedVersions(SupportedVersion.SIOPv2_D12_OID4VP_D20) .build() const requestURI = await rp.createAuthorizationRequestURI({ correlationId: '1234', nonce: 'qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg', state: 'b32f0087fc9816eb813fd11f', }) // Let's test the parsing const parsedAuthReqURI = await op.parseAuthorizationRequestURI(requestURI.encodedUri) expect(parsedAuthReqURI.authorizationRequestPayload).toBeDefined() expect(parsedAuthReqURI.requestObjectJwt).toBeDefined() if (!parsedAuthReqURI.requestObjectJwt) throw new Error('requestObjectJwt is undefined') const verifiedAuthReqWithJWT = await op.verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt) expect(verifiedAuthReqWithJWT.issuer).toMatch(rpMockEntity.did) // The KB property is added to the JWT when the presentation is signed. Passing a VC will make the test fail const dcqlCredentials: DcqlCredential[] = [KB_SD_JWT_PRESENTATION].map((vc) => ({ credential_format: 'vc+sd-jwt', claims: decodeSdJwtVc(vc as string, defaultHasher).decodedPayload as { [x: string]: Json }, vct: decodeSdJwtVc(vc as string, defaultHasher).decodedPayload.vct, })) const queryResult = DcqlQuery.query(sdJwtVcQuery, dcqlCredentials) expect(queryResult).toEqual({ canBeSatisfied: true, credential_matches: { my_credential: { all: [ [ { credential_index: 0, output: { claims: { license: { number: 10, }, user: { name: 'John', }, }, vct: 'https://high-assurance.com/StateBusinessLicense', }, success: true, typed: true, }, ], ], credential_index: 0, output: { claims: { license: { number: 10, }, user: { name: 'John', }, }, vct: 'https://high-assurance.com/StateBusinessLicense', }, success: true, typed: true, }, }, credentials: [ { claims: [ { path: ['license', 'number'], }, { path: ['user', 'name'], }, ], format: 'vc+sd-jwt', id: 'my_credential', meta: { vct_values: ['https://high-assurance.com/StateBusinessLicense'], }, }, ], }) const dcqlPresentation: { [x: string]: string | { [x: string]: Json } } = {} for (const [key, _] of Object.entries(queryResult.credential_matches)) { dcqlPresentation[key] = KB_SD_JWT_PRESENTATION as string | { [x: string]: Json } } const authenticationResponseWithJWT = await op.createAuthorizationResponse(verifiedAuthReqWithJWT, { dcqlResponse: { dcqlPresentation }, }) expect(authenticationResponseWithJWT.response.payload).toBeDefined() expect(authenticationResponseWithJWT.response.idToken).toBeDefined() const verifiedAuthResponseWithJWT = await rp.verifyAuthorizationResponse(authenticationResponseWithJWT.response.payload, { dcqlQuery: sdJwtVcQuery, }) expect(verifiedAuthResponseWithJWT.idToken?.jwt).toBeDefined() expect(verifiedAuthResponseWithJWT.idToken?.payload.nonce).toMatch('qBrR7mqnY3Qr49dAZycPF8FzgE83m6H0c2l0bzP4xSg') }) })