diff --git a/packages/siopv2-oid4vp-rp-auth/src/RPInstance.ts b/packages/siopv2-oid4vp-rp-auth/src/RPInstance.ts index ba6980f97..dd3067e8d 100644 --- a/packages/siopv2-oid4vp-rp-auth/src/RPInstance.ts +++ b/packages/siopv2-oid4vp-rp-auth/src/RPInstance.ts @@ -1,8 +1,14 @@ import { AuthorizationRequest, RP, URI } from '@sphereon/did-auth-siop' import { ICreateAuthRequestArgs, IPEXOptions, IRequiredContext, IRPOptions } from './types/ISIOPv2RP' import { IPresentationDefinition } from '@sphereon/pex' -import { createRPBuilder, getRequestVersion } from './functions' +import { createRPBuilder, getRequestVersion, getSigningAlgo } from './functions' import { v4 as uuidv4 } from 'uuid' +import { JwtIssuer } from '@sphereon/oid4vc-common' +import { + ensureManagedIdentifierResult, + isManagedIdentifierDidResult, + isManagedIdentifierX5cResult, +} from '@sphereon/ssi-sdk-ext.identifier-resolution' export class RPInstance { private _rp: RP | undefined @@ -55,6 +61,25 @@ export class RPInstance { const { correlationId, claims, requestByReferenceURI, responseURI, responseURIType } = createArgs const nonce = createArgs.nonce ?? uuidv4() const state = createArgs.state ?? correlationId + let jwtIssuer: JwtIssuer + const idOpts = this.rpOptions.identifierOpts.idOpts + const resolution = await ensureManagedIdentifierResult(idOpts, context) + if (isManagedIdentifierDidResult(resolution)) { + jwtIssuer = { didUrl: resolution.identifier.did, method: 'did', alg: getSigningAlgo(resolution.key.type) } + } else if (isManagedIdentifierX5cResult(resolution)) { + if (!resolution.issuer) { + return Promise.reject('missing issuer in idOpts') + } + jwtIssuer = { + issuer: resolution.issuer, + x5c: resolution.x5c, + method: 'x5c', + alg: getSigningAlgo(resolution.key.type), + } + } else { + return Promise.reject(Error(`JWT issuer method ${resolution.method} not yet supported`)) + } + return await this.get(context).then((rp) => rp.createAuthorizationRequestURI({ version: getRequestVersion(this.rpOptions), @@ -65,6 +90,7 @@ export class RPInstance { requestByReferenceURI, responseURI, responseURIType, + jwtIssuer, }), ) } diff --git a/packages/siopv2-oid4vp-rp-auth/src/functions.ts b/packages/siopv2-oid4vp-rp-auth/src/functions.ts index c84caf037..43712a9cb 100644 --- a/packages/siopv2-oid4vp-rp-auth/src/functions.ts +++ b/packages/siopv2-oid4vp-rp-auth/src/functions.ts @@ -12,14 +12,16 @@ import { Scope, SubjectType, SupportedVersion, - VerifyJwtCallback + VerifyJwtCallback, } from '@sphereon/did-auth-siop' -import { CreateJwtCallback, JwtHeader, JwtPayload } from '@sphereon/oid4vc-common' +import { CreateJwtCallback, JwtHeader, JwtIssuer, JwtPayload } from '@sphereon/oid4vc-common' import { IPresentationDefinition } from '@sphereon/pex' import { getAgentDIDMethods, getAgentResolver } from '@sphereon/ssi-sdk-ext.did-utils' import { + isManagedIdentifierDidOpts, isManagedIdentifierDidResult, - ManagedIdentifierOptsOrResult + isManagedIdentifierX5cOpts, + ManagedIdentifierOptsOrResult, } from '@sphereon/ssi-sdk-ext.identifier-resolution' import { JwsCompactResult } from '@sphereon/ssi-sdk-ext.jwt-service' import { IVerifySdJwtPresentationResult } from '@sphereon/ssi-sdk.sd-jwt' @@ -29,7 +31,7 @@ import { IVerifyCallbackArgs, IVerifyCredentialResult, VerifyCallback } from '@s // import { KeyAlgo, SuppliedSigner } from '@sphereon/ssi-sdk.core' import { TKeyType } from '@veramo/core' import { createHash } from 'crypto' -import { JWTHeader, JWTVerifyOptions } from 'did-jwt' +import { JWTVerifyOptions } from 'did-jwt' import { Resolvable } from 'did-resolver' import { EventEmitter } from 'events' import { IPEXOptions, IRequiredContext, IRPOptions, ISIOPIdentifierOptions } from './types/ISIOPv2RP' @@ -45,9 +47,9 @@ function getWellKnownDIDVerifyCallback(siopIdentifierOpts: ISIOPIdentifierOption return siopIdentifierOpts.wellknownDIDVerifyCallback ? siopIdentifierOpts.wellknownDIDVerifyCallback : async (args: IVerifyCallbackArgs): Promise => { - const result = await context.agent.verifyCredential({ credential: args.credential, fetchRemoteContexts: true }) - return { verified: result.verified } - } + const result = await context.agent.verifyCredential({ credential: args.credential, fetchRemoteContexts: true }) + return { verified: result.verified } + } } export function getPresentationVerificationCallback(idOpts: ManagedIdentifierOptsOrResult, context: IRequiredContext) { @@ -55,7 +57,7 @@ export function getPresentationVerificationCallback(idOpts: ManagedIdentifierOpt if (CredentialMapper.isSdJwtEncoded(args)) { const result: IVerifySdJwtPresentationResult = await context.agent.verifySdJwtPresentation({ presentation: args, - kb: true + kb: true, }) // fixme: investigate the correct way to handle this return { verified: !!result.payload } @@ -63,7 +65,7 @@ export function getPresentationVerificationCallback(idOpts: ManagedIdentifierOpt const result = await context.agent.verifyPresentation({ presentation: args, fetchRemoteContexts: true, - domain: (await context.agent.identifierManagedGet(idOpts)).kid?.split('#')[0] + domain: (await context.agent.identifierManagedGet(idOpts)).kid?.split('#')[0], }) return { verified: result.verified } } @@ -87,9 +89,9 @@ export async function createRPBuilder(args: { { definitionId: pexOpts.definitionId, version: pexOpts.version, - tenantId: pexOpts.tenantId - } - ] + tenantId: pexOpts.tenantId, + }, + ], }) definition = presentationDefinitionItems.length > 0 ? presentationDefinitionItems[0].definitionPayload : undefined @@ -107,12 +109,12 @@ export async function createRPBuilder(args: { client_name: 'Sphereon', vpFormatsSupported: { jwt_vc: { alg: ['EdDSA', 'ES256K'] }, - jwt_vp: { alg: ['ES256K', 'EdDSA'] } + jwt_vp: { alg: ['ES256K', 'EdDSA'] }, }, scopesSupported: [Scope.OPENID_DIDAUTHN], subjectTypesSupported: [SubjectType.PAIRWISE], subject_syntax_types_supported: didMethods.map((method) => `did:${method}`), - passBy: PassBy.VALUE + passBy: PassBy.VALUE, } const resolution = await context.agent.identifierManagedGet(identifierOpts.idOpts) @@ -121,7 +123,7 @@ export async function createRPBuilder(args: { getAgentResolver(context, { resolverResolution: true, localResolution: true, - uniresolverResolution: rpOpts.identifierOpts.resolveOpts?.noUniversalResolverFallback !== true + uniresolverResolution: rpOpts.identifierOpts.resolveOpts?.noUniversalResolverFallback !== true, }) //todo: probably wise to first look and see if we actually need the hasher to begin with let hasher: Hasher | undefined = rpOpts.credentialOpts?.hasher @@ -134,11 +136,11 @@ export async function createRPBuilder(args: { .withResponseType(ResponseType.VP_TOKEN, PropertyTarget.REQUEST_OBJECT) .withClientId( resolution.issuer ?? (isManagedIdentifierDidResult(resolution) ? resolution.did : resolution.jwkThumbprint), - PropertyTarget.REQUEST_OBJECT + PropertyTarget.REQUEST_OBJECT, ) // todo: move to options fill/correct method .withSupportedVersions( - rpOpts.supportedVersions ?? [SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1, SupportedVersion.SIOPv2_ID1, SupportedVersion.SIOPv2_D11] + rpOpts.supportedVersions ?? [SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1, SupportedVersion.SIOPv2_ID1, SupportedVersion.SIOPv2_D11], ) .withEventEmitter(eventEmitter) .withSessionManager(rpOpts.sessionManager ?? new InMemoryRPSessionManager(eventEmitter)) @@ -146,14 +148,16 @@ export async function createRPBuilder(args: { .withVerifyJwtCallback( rpOpts.verifyJwtCallback ? rpOpts.verifyJwtCallback - : getVerifyJwtCallback({ - resolver, verifyOpts: { - wellknownDIDVerifyCallback: getWellKnownDIDVerifyCallback(rpOpts.identifierOpts, context), - checkLinkedDomain: 'if_present' - } - }, - context - ) + : getVerifyJwtCallback( + { + resolver, + verifyOpts: { + wellknownDIDVerifyCallback: getWellKnownDIDVerifyCallback(rpOpts.identifierOpts, context), + checkLinkedDomain: 'if_present', + }, + }, + context, + ), ) .withRevocationVerification(RevocationVerification.NEVER) .withPresentationVerification(getPresentationVerificationCallback(identifierOpts.idOpts, context)) @@ -187,48 +191,41 @@ export async function createRPBuilder(args: { }*/ //fixme: signcallback and it's return type are not totally compatible with our CreateJwtCallbackBase const createJwtCallback = signCallback(rpOpts.identifierOpts.idOpts, context) - builder.withCreateJwtCallback(createJwtCallback as unknown as CreateJwtCallback) + builder.withCreateJwtCallback(createJwtCallback satisfies CreateJwtCallback) return builder } export function signCallback( idOpts: ManagedIdentifierOptsOrResult, - context: IRequiredContext -): (jwt: { header: JwtHeader; payload: JwtPayload }, kid?: string) => Promise { - return async (jwt: { header: JwtHeader; payload: JwtPayload }, kid?: string) => { - const jwk = jwt.header.jwk - - const resolution = await context.agent.identifierManagedGet(idOpts) - const issuer = jwt.payload.iss || (isManagedIdentifierDidResult(resolution) ? resolution.did : resolution.issuer) - if (!issuer) { - return Promise.reject(Error(`No issuer could be determined from the JWT ${JSON.stringify(jwt)}`)) + context: IRequiredContext, +): (jwtIssuer: JwtIssuer, jwt: { header: JwtHeader; payload: JwtPayload }, kid?: string) => Promise { + return async (jwtIssuer: JwtIssuer, jwt: { header: JwtHeader; payload: JwtPayload }, kid?: string) => { + if (!(isManagedIdentifierDidOpts(idOpts) || isManagedIdentifierX5cOpts(idOpts))) { + return Promise.reject(Error(`JWT issuer method ${jwtIssuer.method} not yet supported`)) } - kid = resolution.kid - const header = { ...jwt.header, ...(kid && !jwk && { kid }) } as Partial - const payload = { ...jwt.payload, ...(issuer && { iss: issuer }) } - if (jwk && header.kid) { - delete header.kid - } - const result: JwsCompactResult = await context.agent.jwtCreateJwsCompactSignature({ - issuer: { identifier: issuer, noIdentifierInHeader: false }, - protectedHeader: header, - payload + // FIXME fix cose-key inference + // @ts-ignore + issuer: { identifier: idOpts.identifier, kmsKeyRef: idOpts.kmsKeyRef, noIdentifierInHeader: false }, + // FIXME fix JWK key_ops + // @ts-ignore + protectedHeader: jwt.header, + payload: jwt.payload, }) return result.jwt } } -function getVerifyJwtCallback(_opts: { - resolver?: Resolvable, - verifyOpts?: JWTVerifyOptions & { - checkLinkedDomain: 'never' | 'if_present' | 'always' - wellknownDIDVerifyCallback?: VerifyCallback - }, - }, - context: IRequiredContext +function getVerifyJwtCallback( + _opts: { + resolver?: Resolvable + verifyOpts?: JWTVerifyOptions & { + checkLinkedDomain: 'never' | 'if_present' | 'always' + wellknownDIDVerifyCallback?: VerifyCallback + } + }, + context: IRequiredContext, ): VerifyJwtCallback { - return async (_jwtVerifier, jwt) => { const result = await context.agent.jwtVerifyJwsSignature({ jws: jwt.raw }) console.log(result.message) @@ -236,11 +233,7 @@ function getVerifyJwtCallback(_opts: { } } - -export async function createRP({ rpOptions, context }: { - rpOptions: IRPOptions; - context: IRequiredContext -}): Promise { +export async function createRP({ rpOptions, context }: { rpOptions: IRPOptions; context: IRequiredContext }): Promise { return (await createRPBuilder({ rpOpts: rpOptions, context })).build() } diff --git a/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts b/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts index b9a73eb38..519723eb5 100644 --- a/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts +++ b/packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts @@ -32,6 +32,7 @@ import { IPDManager, VersionControlMode } from '@sphereon/ssi-sdk.pd-manager' import { CheckLinkedDomain } from '@sphereon/did-auth-siop-adapter' import { ISDJwtPlugin } from '@sphereon/ssi-sdk.sd-jwt' import { IJwtService } from '@sphereon/ssi-sdk-ext.jwt-service' +import { JwtIssuer } from '@sphereon/oid4vc-common' export enum VerifiedDataMode { NONE = 'none', @@ -65,6 +66,7 @@ export interface ICreateAuthRequestArgs { correlationId: string responseURIType: ResponseURIType responseURI: string + jwtIssuer?: JwtIssuer requestByReferenceURI?: string nonce?: string state?: string