Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/SPRIND-25_oid4vp_rp' int…
Browse files Browse the repository at this point in the history
…o feature/SPRIND-25_oid4vp_rp
  • Loading branch information
nklomp committed Aug 27, 2024
2 parents 6de5bc3 + 9706e43 commit 2436799
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 60 deletions.
28 changes: 27 additions & 1 deletion packages/siopv2-oid4vp-rp-auth/src/RPInstance.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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),
Expand All @@ -65,6 +90,7 @@ export class RPInstance {
requestByReferenceURI,
responseURI,
responseURIType,
jwtIssuer,
}),
)
}
Expand Down
111 changes: 52 additions & 59 deletions packages/siopv2-oid4vp-rp-auth/src/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -45,25 +47,25 @@ function getWellKnownDIDVerifyCallback(siopIdentifierOpts: ISIOPIdentifierOption
return siopIdentifierOpts.wellknownDIDVerifyCallback
? siopIdentifierOpts.wellknownDIDVerifyCallback
: async (args: IVerifyCallbackArgs): Promise<IVerifyCredentialResult> => {
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) {
async function presentationVerificationCallback(args: any): Promise<PresentationVerificationResult> {
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 }
}
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 }
}
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -134,26 +136,28 @@ 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))
.withClientMetadata(rpOpts.clientMetadataOpts ?? defaultClientMetadata, PropertyTarget.REQUEST_OBJECT)
.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))
Expand Down Expand Up @@ -187,60 +191,49 @@ 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<any>)
builder.withCreateJwtCallback(createJwtCallback satisfies CreateJwtCallback<any>)
return builder
}

export function signCallback(
idOpts: ManagedIdentifierOptsOrResult,
context: IRequiredContext
): (jwt: { header: JwtHeader; payload: JwtPayload }, kid?: string) => Promise<string> {
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<string> {
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<JWTHeader>
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)
return !result.error
}
}


export async function createRP({ rpOptions, context }: {
rpOptions: IRPOptions;
context: IRequiredContext
}): Promise<RP> {
export async function createRP({ rpOptions, context }: { rpOptions: IRPOptions; context: IRequiredContext }): Promise<RP> {
return (await createRPBuilder({ rpOpts: rpOptions, context })).build()
}

Expand Down
2 changes: 2 additions & 0 deletions packages/siopv2-oid4vp-rp-auth/src/types/ISIOPv2RP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -65,6 +66,7 @@ export interface ICreateAuthRequestArgs {
correlationId: string
responseURIType: ResponseURIType
responseURI: string
jwtIssuer?: JwtIssuer
requestByReferenceURI?: string
nonce?: string
state?: string
Expand Down

0 comments on commit 2436799

Please sign in to comment.