diff --git a/package.json b/package.json index 8af5fdb1..0040c1b6 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@sphereon/did-uni-client": "^0.6.0", - "@sphereon/pex": "2.1.3-unstable.1", + "@sphereon/pex": "2.1.3-unstable.6", "@sphereon/pex-models": "^2.1.1", "@sphereon/ssi-types": "^0.17.5", "@sphereon/wellknown-dids-client": "^0.1.3", diff --git a/src/authorization-request/AuthorizationRequest.ts b/src/authorization-request/AuthorizationRequest.ts index 5ae4c5d4..9fec4992 100644 --- a/src/authorization-request/AuthorizationRequest.ts +++ b/src/authorization-request/AuthorizationRequest.ts @@ -11,21 +11,18 @@ import { PassBy, RequestObjectJwt, RequestObjectPayload, - RequestStateInfo, ResponseURIType, + RequestStateInfo, + ResponseURIType, RPRegistrationMetadataPayload, Schema, SIOPErrors, SupportedVersion, VerifiedAuthorizationRequest, - VerifiedJWT + VerifiedJWT, } from '../types'; import { assertValidAuthorizationRequestOpts, assertValidVerifyAuthorizationRequestOpts } from './Opts'; -import { - assertValidRPRegistrationMedataPayload, - checkWellknownDIDFromRequest, - createAuthorizationRequestPayload -} from './Payload'; +import { assertValidRPRegistrationMedataPayload, checkWellknownDIDFromRequest, createAuthorizationRequestPayload } from './Payload'; import { URI } from './URI'; import { CreateAuthorizationRequestOpts, VerifyAuthorizationRequestOpts } from './types'; @@ -60,6 +57,7 @@ export class AuthorizationRequest { } public static async fromOpts(opts: CreateAuthorizationRequestOpts, requestObject?: RequestObject): Promise { + // todo: response_uri/redirect_uri is not hooked up from opts! if (!opts || !opts.requestObject) { throw Error(SIOPErrors.BAD_PARAMS); } @@ -127,7 +125,7 @@ export class AuthorizationRequest { const options: JWTVerifyOptions = { ...opts.verification?.resolveOpts?.jwtVerifyOpts, resolver, - audience: getAudience(jwt) + audience: getAudience(jwt), }; verifiedJwt = await verifyDidJWT(jwt, resolver, options); @@ -170,7 +168,7 @@ export class AuthorizationRequest { } else if (mergedPayload.redirect_uri) { responseURIType = 'redirect_uri'; responseURI = mergedPayload.redirect_uri; - } else if (mergedPayload.redirect_uri) { + } else if (mergedPayload.response_uri) { responseURIType = 'response_uri'; responseURI = mergedPayload.response_uri; } else if (mergedPayload.client_id_scheme === 'redirect_uri' && mergedPayload.client_id) { @@ -198,7 +196,7 @@ export class AuthorizationRequest { registrationMetadataPayload, requestObject: this.requestObject, authorizationRequestPayload: this.payload, - versions: await this.getSupportedVersionsFromPayload() + versions: await this.getSupportedVersionsFromPayload(), }; } @@ -238,7 +236,7 @@ export class AuthorizationRequest { client_id: this.options.clientMetadata.client_id, iat: requestObject.iat ?? this.payload.iat, nonce: requestObject.nonce ?? this.payload.nonce, - state: this.payload.state + state: this.payload.state, }; } @@ -253,7 +251,7 @@ export class AuthorizationRequest { } public async mergedPayloads(): Promise { - return { ...this.payload, ...(await this.requestObject.getPayload()) }; + return { ...this.payload, ...(this.requestObject && await this.requestObject.getPayload()) }; } public async getPresentationDefinitions(version?: SupportedVersion): Promise { diff --git a/src/authorization-response/PresentationExchange.ts b/src/authorization-response/PresentationExchange.ts index 319c425c..15607b18 100644 --- a/src/authorization-response/PresentationExchange.ts +++ b/src/authorization-response/PresentationExchange.ts @@ -61,12 +61,14 @@ export class PresentationExchange { const signOptions: VerifiablePresentationFromOpts = { ...options, proofOptions: { + ...options.proofOptions, proofPurpose: options?.proofOptions?.proofPurpose ?? IProofPurpose.authentication, type: options?.proofOptions?.type ?? IProofType.EcdsaSecp256k1Signature2019, challenge: options?.proofOptions?.challenge, domain: options?.proofOptions?.domain, }, signatureOptions: { + ...options.signatureOptions, verificationMethod: options?.signatureOptions?.verificationMethod, keyEncoding: options?.signatureOptions?.keyEncoding ?? KeyEncoding.Hex, }, diff --git a/src/authorization-response/types.ts b/src/authorization-response/types.ts index aee0a4d1..4abea789 100644 --- a/src/authorization-response/types.ts +++ b/src/authorization-response/types.ts @@ -11,6 +11,7 @@ import { NoSignature, ResponseMode, ResponseRegistrationOpts, + ResponseURIType, SuppliedSignature, SupportedVersion, VerifiablePresentationWithFormat, @@ -19,8 +20,9 @@ import { import { AuthorizationResponse } from './AuthorizationResponse'; export interface AuthorizationResponseOpts { - redirectUri?: string; // It's typically comes from the request opts as a measure to prevent hijacking. - responseUri?: string; // Alternative to the redirectUri, used when response_mode is `direct_post` + // redirectUri?: string; // It's typically comes from the request opts as a measure to prevent hijacking. + responseURI?: string; // This is either the redirect URI or response URI. See also responseURIType. response URI is used when response_mode is `direct_post` + responseURIType?: ResponseURIType; registration?: ResponseRegistrationOpts; checkLinkedDomain?: CheckLinkedDomain; diff --git a/src/op/OP.ts b/src/op/OP.ts index 8f72c13a..162ee610 100644 --- a/src/op/OP.ts +++ b/src/op/OP.ts @@ -177,7 +177,7 @@ export class OP { const payload = response.payload; const idToken = await response.idToken?.payload(); - const responseUri = authorizationResponse.responseURI || idToken?.aud; + const responseUri = authorizationResponse.responseURI ?? idToken?.aud; if (!responseUri) { throw Error('No response URI present'); } @@ -233,6 +233,7 @@ export class OP { } // We are taking the whole presentationExchange object from a certain location const presentationExchange = opts.presentationExchange ?? this._createResponseOptions.presentationExchange; + const responseURI = opts.audience ?? this._createResponseOptions.responseURI; return { ...this._createResponseOptions, ...opts, @@ -242,7 +243,9 @@ export class OP { }, ...(presentationExchange && { presentationExchange }), registration: { ...this._createResponseOptions?.registration, issuer }, - redirectUri: opts.audience ?? this._createResponseOptions.redirectUri, + responseURI, + responseURIType: + this._createResponseOptions.responseURIType ?? (version < SupportedVersion.SIOPv2_D12_OID4VP_D18 && responseURI ? 'redirect_uri' : undefined), }; } diff --git a/src/rp/RP.ts b/src/rp/RP.ts index 72cb8595..2be583e2 100644 --- a/src/rp/RP.ts +++ b/src/rp/RP.ts @@ -23,6 +23,7 @@ import { InternalVerification, PassBy, RegisterEventListener, + ResponseURIType, SIOPErrors, SupportedVersion, VerifiedAuthorizationResponse, @@ -70,7 +71,8 @@ export class RP { claims?: ClaimPayloadCommonOpts | RequestPropertyWithTargets; version?: SupportedVersion; requestByReferenceURI?: string; - redirectURI?: string; + redirectURI_TODO_NOT_IMPLEMENTED?: string; // todo: response_uri/redirect_uri is not hooked up from opts! + responseURIType_TODO_NOT_IMEPLEMENTED?: ResponseURIType; // todo: response_uri/redirect_uri is not hooked up from opts! }): Promise { const authorizationRequestOpts = this.newAuthorizationRequestOpts(opts); return AuthorizationRequest.fromOpts(authorizationRequestOpts) diff --git a/src/schemas/AuthorizationResponseOpts.schema.ts b/src/schemas/AuthorizationResponseOpts.schema.ts index f04d4399..29c1b209 100644 --- a/src/schemas/AuthorizationResponseOpts.schema.ts +++ b/src/schemas/AuthorizationResponseOpts.schema.ts @@ -6,11 +6,11 @@ export const AuthorizationResponseOptsSchemaObj = { "AuthorizationResponseOpts": { "type": "object", "properties": { - "redirectUri": { + "responseURI": { "type": "string" }, - "responseUri": { - "type": "string" + "responseURIType": { + "$ref": "#/definitions/ResponseURIType" }, "registration": { "$ref": "#/definitions/ResponseRegistrationOpts" @@ -61,6 +61,13 @@ export const AuthorizationResponseOptsSchemaObj = { }, "additionalProperties": false }, + "ResponseURIType": { + "type": "string", + "enum": [ + "response_uri", + "redirect_uri" + ] + }, "ResponseRegistrationOpts": { "anyOf": [ { diff --git a/src/types/SIOP.types.ts b/src/types/SIOP.types.ts index ad0a3af1..f00c5322 100644 --- a/src/types/SIOP.types.ts +++ b/src/types/SIOP.types.ts @@ -34,7 +34,7 @@ export interface RequestObjectPayload extends RequestCommonPayload, JWTPayload { scope: string; // REQUIRED. As specified in Section 3.1.2 of [OpenID.Core]. response_type: ResponseType | string; // REQUIRED. Constant string value id_token. client_id: string; // REQUIRED. RP's identifier at the Self-Issued OP. - client_id_scheme?: ClientIdScheme // The client_id_scheme enables deployments of this specification to use different mechanisms to obtain and validate metadata of the Verifier beyond the scope of [RFC6749]. The term client_id_scheme is used since the Verifier is acting as an OAuth 2.0 Client. + client_id_scheme?: ClientIdScheme; // The client_id_scheme enables deployments of this specification to use different mechanisms to obtain and validate metadata of the Verifier beyond the scope of [RFC6749]. The term client_id_scheme is used since the Verifier is acting as an OAuth 2.0 Client. redirect_uri?: string; // REQUIRED before OID4VP v18, now optional because of response_uri. URI to which the Self-Issued OP Response will be sent response_uri?: string; // New since OID4VP18 OPTIONAL. The Response URI to which the Wallet MUST send the Authorization Response using an HTTPS POST request as defined by the Response Mode direct_post. The Response URI receives all Authorization Response parameters as defined by the respective Response Type. When the response_uri parameter is present, the redirect_uri Authorization Request parameter MUST NOT be present. If the redirect_uri Authorization Request parameter is present when the Response Mode is direct_post, the Wallet MUST return an invalid_request Authorization Response error. nonce: string; @@ -111,10 +111,10 @@ export interface RequestRegistrationPayloadProperties { registration_uri?: string; // OPTIONAL. This parameter is used by the RP to provide information about itself to a Self-Issued OP that would normally be provided to an OP during Dynamic RP Registration, as specified in 2.2.1. } -export type ResponseURIType = 'response_uri' | 'redirect_uri' +export type ResponseURIType = 'response_uri' | 'redirect_uri'; export interface VerifiedAuthorizationRequest extends Partial { - responseURIType: ResponseURIType + responseURIType: ResponseURIType; responseURI?: string; clientIdScheme?: string; correlationId: string; diff --git a/test/AuthenticationRequest.verify.spec.ts b/test/AuthenticationRequest.verify.spec.ts index 1621fbc4..8f80bb64 100644 --- a/test/AuthenticationRequest.verify.spec.ts +++ b/test/AuthenticationRequest.verify.spec.ts @@ -442,29 +442,3 @@ describe('OP and RP communication should', () => { expect(() => metadata.verify()).toThrowError(SIOPErrors.CREDENTIALS_FORMATS_NOT_PROVIDED); }); }); - -describe('Mattr OID4VP v18 credential offer', () => { - test('should verify', async () => { - const authorizationRequest = await AuthorizationRequest.fromUriOrJwt( - 'openid4vp://authorize?client_id=https%3A%2F%2Flaunchpad.mattrlabs.com%2Fapi%2Fvp%2Fcallback&client_id_scheme=redirect_uri&response_uri=https%3A%2F%2Flaunchpad.mattrlabs.com%2Fapi%2Fvp%2Fcallback&response_type=vp_token&response_mode=direct_post&presentation_definition_uri=https%3A%2F%2Flaunchpad.mattrlabs.com%2Fapi%2Fvp%2Frequest%3Fstate%3DfzoNJFAU3UeXQuLAsFAyWw&nonce=F-9MSA0Lb0-_MbW4bCzZiA&state=fzoNJFAU3UeXQuLAsFAyWw' - ); - console.log(JSON.stringify(authorizationRequest.payload, null, 2)); - console.log(JSON.stringify(await authorizationRequest.getSupportedVersionsFromPayload())); - - const verification = await authorizationRequest.verify({ - correlationId: 'test', - verification: { - mode: VerificationMode.INTERNAL, - resolveOpts: {}, - }, - }); - - console.log(JSON.stringify(verification)); - expect(verification).toBeDefined(); - expect(verification.versions).toEqual([SupportedVersion.SIOPv2_D12_OID4VP_D18]); - - /** - * pd value: {"id":"dae5d9b6-8145-4297-99b2-b8fcc5abb5ad","input_descriptors":[{"id":"OpenBadgeCredential","format":{"jwt_vc_json":{"alg":["EdDSA"]},"jwt_vc":{"alg":["EdDSA"]}},"constraints":{"fields":[{"path":["$.vc.type"],"filter":{"type":"array","items":{"type":"string"},"contains":{"const":"OpenBadgeCredential"}}}]}}]} - */ - }); -}); diff --git a/test/AuthenticationResponse.response.spec.ts b/test/AuthenticationResponse.response.spec.ts index 3895dc52..bdb98cd6 100644 --- a/test/AuthenticationResponse.response.spec.ts +++ b/test/AuthenticationResponse.response.spec.ts @@ -51,7 +51,8 @@ const EXAMPLE_REDIRECT_URL = 'https://acme.com/hello'; describe('create JWT from Request JWT should', () => { const responseOpts: AuthorizationResponseOpts = { checkLinkedDomain: CheckLinkedDomain.NEVER, - redirectUri: EXAMPLE_REDIRECT_URL, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', responseMode: ResponseMode.POST, registration: { authorizationEndpoint: 'www.myauthorizationendpoint.com', @@ -163,7 +164,8 @@ describe('create JWT from Request JWT should', () => { }; const responseOpts: AuthorizationResponseOpts = { checkLinkedDomain: CheckLinkedDomain.NEVER, - redirectUri: EXAMPLE_REDIRECT_URL, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', registration: { authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], @@ -252,7 +254,8 @@ describe('create JWT from Request JWT should', () => { }; const responseOpts: AuthorizationResponseOpts = { checkLinkedDomain: CheckLinkedDomain.NEVER, - redirectUri: EXAMPLE_REDIRECT_URL, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', registration: { authorizationEndpoint: 'www.myauthorizationendpoint.com', idTokenSigningAlgValuesSupported: [SigningAlgo.EDDSA, SigningAlgo.ES256], @@ -413,7 +416,8 @@ describe('create JWT from Request JWT should', () => { ); const responseOpts: AuthorizationResponseOpts = { checkLinkedDomain: CheckLinkedDomain.NEVER, - redirectUri: EXAMPLE_REDIRECT_URL, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', registration: { authorizationEndpoint: 'www.myauthorizationendpoint.com', issuer: ResponseIss.SELF_ISSUED_V2, @@ -586,7 +590,8 @@ describe('create JWT from Request JWT should', () => { ); const responseOpts: AuthorizationResponseOpts = { checkLinkedDomain: CheckLinkedDomain.NEVER, - redirectUri: EXAMPLE_REDIRECT_URL, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', registration: { authorizationEndpoint: 'www.myauthorizationendpoint.com', issuer: ResponseIss.SELF_ISSUED_V2, diff --git a/test/OP.request.spec.ts b/test/OP.request.spec.ts index 35d9eb51..518a2212 100644 --- a/test/OP.request.spec.ts +++ b/test/OP.request.spec.ts @@ -72,7 +72,8 @@ describe('OP OPBuilder should', () => { describe('OP should', () => { const responseOpts: AuthorizationResponseOpts = { checkLinkedDomain: CheckLinkedDomain.NEVER, - redirectUri: EXAMPLE_REDIRECT_URL, + responseURI: EXAMPLE_REDIRECT_URL, + responseURIType: 'redirect_uri', signature: { hexPrivateKey: HEX_KEY, did: DID, diff --git a/test/e2e/mattr.launchpad.spec.ts b/test/e2e/mattr.launchpad.spec.ts index 459dac54..a3b5bf74 100644 --- a/test/e2e/mattr.launchpad.spec.ts +++ b/test/e2e/mattr.launchpad.spec.ts @@ -1,9 +1,19 @@ +import { PresentationSignCallBackParams, PresentationSubmissionLocation } from '@sphereon/pex'; +import { W3CVerifiablePresentation } from '@sphereon/ssi-types'; import * as ed25519 from '@transmute/did-key-ed25519'; -import * as u8a from 'uint8arrays'; import { fetch } from 'cross-fetch'; -import { JWK } from 'jose'; +import { importJWK, JWK, SignJWT } from 'jose'; +import * as u8a from 'uint8arrays'; -import { AuthorizationRequest, OP, VerificationMode } from '../../src'; +import { + AuthorizationRequest, + AuthorizationResponse, + PresentationDefinitionWithLocation, + PresentationExchange, + SigningAlgo, + SupportedVersion, + VerificationMode +} from '../../src'; export interface InitiateOfferRequest { types: string[]; @@ -19,6 +29,9 @@ export const UNIT_TEST_TIMEOUT = 30000; export const VP_CREATE_URL = 'https://launchpad.mattrlabs.com/api/vp/create'; +export const OPENBADGE_JWT_VC = + 'eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp3ZWI6bGF1bmNocGFkLnZpaS5lbGVjdHJvbi5tYXR0cmxhYnMuaW8jNkJoRk1DR1RKZyJ9.eyJpc3MiOiJkaWQ6d2ViOmxhdW5jaHBhZC52aWkuZWxlY3Ryb24ubWF0dHJsYWJzLmlvIiwic3ViIjoiZGlkOmtleTp6Nk1raXRHVmduTGRORlpqbUE5WEpwQThrM29lakVudU1GN205NkJEN3BaTGprWTIiLCJuYmYiOjE2OTYzNjA1MTEsImV4cCI6MTcyNzk4MjkxMSwidmMiOnsibmFtZSI6IkV4YW1wbGUgVW5pdmVyc2l0eSBEZWdyZWUiLCJkZXNjcmlwdGlvbiI6IkpGRiBQbHVnZmVzdCAzIE9wZW5CYWRnZSBDcmVkZW50aWFsIiwiY3JlZGVudGlhbEJyYW5kaW5nIjp7ImJhY2tncm91bmRDb2xvciI6IiM0NjRjNDkifSwiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL21hdHRyLmdsb2JhbC9jb250ZXh0cy92Yy1leHRlbnNpb25zL3YyIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjIuanNvbiIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvb2IvdjNwMC9leHRlbnNpb25zLmpzb24iLCJodHRwczovL3czaWQub3JnL3ZjLXJldm9jYXRpb24tbGlzdC0yMDIwL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1raXRHVmduTGRORlpqbUE5WEpwQThrM29lakVudU1GN205NkJEN3BaTGprWTIiLCJ0eXBlIjpbIkFjaGlldmVtZW50U3ViamVjdCJdLCJhY2hpZXZlbWVudCI6eyJpZCI6Imh0dHBzOi8vZXhhbXBsZS5jb20vYWNoaWV2ZW1lbnRzLzIxc3QtY2VudHVyeS1za2lsbHMvdGVhbXdvcmsiLCJuYW1lIjoiVGVhbXdvcmsiLCJ0eXBlIjpbIkFjaGlldmVtZW50Il0sImltYWdlIjp7ImlkIjoiaHR0cHM6Ly93M2MtY2NnLmdpdGh1Yi5pby92Yy1lZC9wbHVnZmVzdC0zLTIwMjMvaW1hZ2VzL0pGRi1WQy1FRFUtUExVR0ZFU1QzLWJhZGdlLWltYWdlLnBuZyIsInR5cGUiOiJJbWFnZSJ9LCJjcml0ZXJpYSI6eyJuYXJyYXRpdmUiOiJUZWFtIG1lbWJlcnMgYXJlIG5vbWluYXRlZCBmb3IgdGhpcyBiYWRnZSBieSB0aGVpciBwZWVycyBhbmQgcmVjb2duaXplZCB1cG9uIHJldmlldyBieSBFeGFtcGxlIENvcnAgbWFuYWdlbWVudC4ifSwiZGVzY3JpcHRpb24iOiJUaGlzIGJhZGdlIHJlY29nbml6ZXMgdGhlIGRldmVsb3BtZW50IG9mIHRoZSBjYXBhY2l0eSB0byBjb2xsYWJvcmF0ZSB3aXRoaW4gYSBncm91cCBlbnZpcm9ubWVudC4ifX0sImlzc3VlciI6eyJpZCI6ImRpZDp3ZWI6bGF1bmNocGFkLnZpaS5lbGVjdHJvbi5tYXR0cmxhYnMuaW8iLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5IiwiaWNvblVybCI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMS0yMDIyL2ltYWdlcy9KRkZfTG9nb0xvY2t1cC5wbmciLCJpbWFnZSI6Imh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vdmMtZWQvcGx1Z2Zlc3QtMS0yMDIyL2ltYWdlcy9KRkZfTG9nb0xvY2t1cC5wbmcifX19.JDQ5kp_nvqJbL9Q8o2xIdt_r_WG0cB1o-Boy1RiDZhXRlVTgwAxvCa41OiL97VnbovN98tL7VtXbM6slAt6TBg'; + export const jwk: JWK = { crv: 'Ed25519', d: 'kTRm0aONHYwNPA-w_DtjMHUIWjE3K70qgCIhWojZ0eU', @@ -45,7 +58,6 @@ export const generateDid = async (opts?: { seed?: Uint8Array }) => { return { keys, didDocument }; }; - describe('OID4VCI-Client using Mattr issuer should', () => { async function test(format: string | string[]) { const did = await generateDid({ seed: u8a.fromString('913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5', 'base16') }); @@ -58,17 +70,35 @@ describe('OID4VCI-Client using Mattr issuer should', () => { expect(state).toBeDefined(); expect(nonce).toBeDefined(); - const auth = await AuthorizationRequest.verify(authorizeRequestUri, { - correlationId: 'test', verification: { + const correlationId = 'test'; + + const verifiedAuthRequest = await AuthorizationRequest.verify(authorizeRequestUri, { + correlationId, + verification: { mode: VerificationMode.INTERNAL, resolveOpts: {} } }); - expect(auth).toBeDefined() - expect(auth.presentationDefinitions).toHaveLength(1) - - - OP.builder().addDidMethod('key'); + expect(verifiedAuthRequest).toBeDefined(); + expect(verifiedAuthRequest.presentationDefinitions).toHaveLength(1); + + const pex = new PresentationExchange({ allDIDs: [didStr], allVerifiableCredentials: [OPENBADGE_JWT_VC] }); + const pd: PresentationDefinitionWithLocation[] = await PresentationExchange.findValidPresentationDefinitions( + verifiedAuthRequest.authorizationRequestPayload + ); + await pex.selectVerifiableCredentialsForSubmission(pd[0].definition); + const verifiablePresentationResult = await pex.createVerifiablePresentation(pd[0].definition, [OPENBADGE_JWT_VC], presentationSignCalback, { presentationSubmissionLocation: PresentationSubmissionLocation.EXTERNAL, proofOptions: { nonce }, holderDID: didStr }); + + const authResponse = await AuthorizationResponse.fromVerifiedAuthorizationRequest(verifiedAuthRequest, + { presentationExchange: { verifiablePresentations: [verifiablePresentationResult.verifiablePresentation], presentationSubmission: verifiablePresentationResult.presentationSubmission }, + signature: {hexPrivateKey: '913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5', did: didStr, kid, alg: SigningAlgo.EDDSA}}, + { correlationId, verification: { mode: VerificationMode.INTERNAL, resolveOpts: {} }, nonce, state }); + + expect(authResponse).toBeDefined() + expect(authResponse.payload).toBeDefined() + expect(authResponse.payload.presentation_submission).toBeDefined() + expect(authResponse.payload.vp_token).toBeDefined() + console.log(JSON.stringify(authResponse)); } it( @@ -87,7 +117,6 @@ describe('OID4VCI-Client using Mattr issuer should', () => { ); }); - async function getOffer(types: string | string[]): Promise { const credentialOffer = await fetch(VP_CREATE_URL, { method: 'post', @@ -105,13 +134,45 @@ async function getOffer(types: string | string[]): Promise { +describe('Mattr OID4VP v18 credential offer', () => { + test('should verify', async () => { + const offer = await getOffer('OpenBadgeCredential'); + const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(offer.authorizeRequestUri); + console.log(JSON.stringify(authorizationRequest.payload, null, 2)); + console.log(JSON.stringify(await authorizationRequest.getSupportedVersionsFromPayload())); + + const verification = await authorizationRequest.verify({ + correlationId: 'test', + verification: { + mode: VerificationMode.INTERNAL, + resolveOpts: {} + } + }); + + console.log(JSON.stringify(verification)); + expect(verification).toBeDefined(); + expect(verification.versions).toEqual([SupportedVersion.SIOPv2_D12_OID4VP_D18]); + + /** + * pd value: {"id":"dae5d9b6-8145-4297-99b2-b8fcc5abb5ad","input_descriptors":[{"id":"OpenBadgeCredential","format":{"jwt_vc_json":{"alg":["EdDSA"]},"jwt_vc":{"alg":["EdDSA"]}},"constraints":{"fields":[{"path":["$.vc.type"],"filter":{"type":"array","items":{"type":"string"},"contains":{"const":"OpenBadgeCredential"}}}]}}]} + */ + }); +}); + +async function presentationSignCalback(args: PresentationSignCallBackParams): Promise { + const importedJwk = await importJWK(jwk, 'EdDSA'); - return await new SignJWT({ ...args.payload }) - .setProtectedHeader({ ...args.header }) + const jwt = await new SignJWT({ vp: {...args.presentation}, nonce: args.options.proofOptions.nonce, iss: args.options.holderDID }) + .setProtectedHeader({ + 'typ': 'JWT', + 'alg': 'EdDSA', + kid + }) .setIssuedAt() .setExpirationTime('2h') .sign(importedJwk); + + console.log(`VP: ${jwt}`); + return jwt; } -*/ + diff --git a/yarn.lock b/yarn.lock index 2fe5e296..9575752b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1436,10 +1436,10 @@ resolved "https://registry.yarnpkg.com/@sphereon/pex-models/-/pex-models-2.1.1.tgz#399e529db2a7e3b9abbd7314cdba619ceb6cb758" integrity sha512-0UX/CMwgiJSxzuBn6SLOTSKkm+uPq3dkNjl8w4EtppXp6zBB4lQMd1mJX7OifX5Bp5vPUfoz7bj2B+yyDtbZww== -"@sphereon/pex@2.1.3-unstable.1": - version "2.1.3-unstable.1" - resolved "https://registry.yarnpkg.com/@sphereon/pex/-/pex-2.1.3-unstable.1.tgz#ad68b66f18928e69bdc369641ddc69fd01f4956e" - integrity sha512-aRPWOC30YoJVlqfA0VZy/0blwFIlfomv/+cpKgn4Zth8AU4QsH8a/oeFmrsRp2k7K9KDJ91UAmI3Y4wv3EaU2A== +"@sphereon/pex@2.1.3-unstable.6": + version "2.1.3-unstable.6" + resolved "https://registry.yarnpkg.com/@sphereon/pex/-/pex-2.1.3-unstable.6.tgz#0934c07d615551bef2092673824d60f58530378a" + integrity sha512-8vyYwGGqtxfmlwJuSe2lFSI8sedFV8Y6DR1o+bqMe18cUtgJUZ2XlsDLbq9r0npC5URJQJWOUo5ZUoGWuRJfCg== dependencies: "@astronautlabs/jsonpath" "^1.1.2" "@sphereon/pex-models" "^2.1.1"