@sphereon/did-auth-siop
Version:
Self Issued OpenID V2 (SIOPv2) and OpenID 4 Verifiable Presentations (OID4VP)
554 lines (510 loc) • 25.6 kB
text/typescript
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')
})
})