diff --git a/package.json b/package.json index c088b28e..7fece3bc 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@credo-ts/anoncreds": "0.6.1", "@credo-ts/askar": "0.6.1", "@credo-ts/core": "0.6.1", + "@credo-ts/didcomm": "0.6.1", "@credo-ts/indy-vdr": "0.6.1", "@credo-ts/node": "0.6.1", "@credo-ts/openid4vc": "0.6.1", diff --git a/src/cliAgent.ts b/src/cliAgent.ts index 516dddfc..1a603e4f 100644 --- a/src/cliAgent.ts +++ b/src/cliAgent.ts @@ -1,6 +1,7 @@ // Note: For now we need to import askar-nodejs at the top to handle the undefined askar issue // Refer from: https://github.com/credebl/mobile-sdk/blob/main/packages/ssi/src/wallet/wallet.ts import '@openwallet-foundation/askar-nodejs' +import type { AskarModuleConfigStoreOptions } from '@credo-ts/askar' import type { InitConfig } from '@credo-ts/core' import type { IndyVdrPoolConfig } from '@credo-ts/indy-vdr' @@ -14,7 +15,7 @@ import { DidCommCredentialV1Protocol, DidCommProofV1Protocol, } from '@credo-ts/anoncreds' -import { AskarModule, AskarModuleConfigStoreOptions, AskarMultiWalletDatabaseScheme } from '@credo-ts/askar' +import { AskarModule, AskarMultiWalletDatabaseScheme } from '@credo-ts/askar' import { DidsModule, W3cCredentialsModule, @@ -26,6 +27,8 @@ import { LogLevel, Agent, X509Module, + JwkDidRegistrar, + JwkDidResolver, } from '@credo-ts/core' import { DidCommHttpOutboundTransport, @@ -46,29 +49,22 @@ import { IndyVdrIndyDidRegistrar, } from '@credo-ts/indy-vdr' import { agentDependencies, DidCommHttpInboundTransport, DidCommWsInboundTransport } from '@credo-ts/node' +import { OpenId4VcHolderModule, OpenId4VcModule } from '@credo-ts/openid4vc' import { QuestionAnswerModule } from '@credo-ts/question-answer' import { TenantsModule } from '@credo-ts/tenants' import { anoncreds } from '@hyperledger/anoncreds-nodejs' -import { askar } from '@openwallet-foundation/askar-nodejs' import { indyVdr } from '@hyperledger/indy-vdr-nodejs' +import { askar } from '@openwallet-foundation/askar-nodejs' import axios from 'axios' +import bodyParser from 'body-parser' +import express from 'express' import { readFile } from 'fs/promises' import { IndicioAcceptanceMechanism, IndicioTransactionAuthorAgreement, Network, NetworkName } from './enums' import { setupServer } from './server' import { generateSecretKey } from './utils/helpers' import { TsLogger } from './utils/logger' -import { OpenId4VcHolderModule, OpenId4VcIssuerModule, OpenId4VcVerifierModule } from '@credo-ts/openid4vc' -import { - getCredentialRequestToCredentialMapper, - getTrustedCerts, -} from './utils/oid4vc-agent' -import bodyParser from 'body-parser' - -import express from 'express' - -const openId4VpApp = express() -const openId4VcApp = express() +import { getMixedCredentialRequestToCredentialMapper, getTrustedCerts } from './utils/oid4vc-agent' export type Transports = 'ws' | 'http' export type InboundTransport = { @@ -128,7 +124,15 @@ export async function readRestConfig(path: string) { export type RestMultiTenantAgentModules = Awaited> export type RestAgentModules = Awaited> - +function requireEnv(name: string): string { + const value = process.env[name] + if (!value) { + throw new Error(`Missing environment variable: ${name}`) + } + return value +} +const expressApp = express() +expressApp.disable('x-powered-by') // TODO: add object const getModules = ( networkConfig: [IndyVdrPoolConfig, ...IndyVdrPoolConfig[]], @@ -141,7 +145,7 @@ const getModules = ( autoAcceptCredentials: DidCommAutoAcceptCredential, autoAcceptProofs: DidCommAutoAcceptProof, walletScheme: AskarMultiWalletDatabaseScheme, - storeOptions: AskarModuleConfigStoreOptions + storeOptions: AskarModuleConfigStoreOptions, ) => { const legacyIndyCredentialFormat = new LegacyIndyDidCommCredentialFormatService() const legacyIndyProofFormat = new LegacyIndyDidCommProofFormatService() @@ -149,26 +153,33 @@ const getModules = ( const anonCredsCredentialFormatService = new AnonCredsDidCommCredentialFormatService() const anonCredsProofFormatService = new AnonCredsDidCommProofFormatService() const presentationExchangeProofFormatService = new DidCommDifPresentationExchangeProofFormatService() + return { askar: new AskarModule({ askar, store: { - ...storeOptions + ...storeOptions, }, multiWalletDatabaseScheme: walletScheme || AskarMultiWalletDatabaseScheme.ProfilePerWallet, - }), indyVdr: new IndyVdrModule({ indyVdr, networks: networkConfig, }), - dids: new DidsModule({ - registrars: [new IndyVdrIndyDidRegistrar(), new KeyDidRegistrar() + registrars: [ + new IndyVdrIndyDidRegistrar(), + new KeyDidRegistrar(), + new JwkDidRegistrar(), // , new PolygonDidRegistrar() ], - resolvers: [new IndyVdrIndyDidResolver(), new KeyDidResolver(), new WebDidResolver() + resolvers: [ + new IndyVdrIndyDidResolver(), + new KeyDidResolver(), + new WebDidResolver(), + new JwkDidResolver(), + new KeyDidResolver(), // , new PolygonDidResolver() ], }), @@ -210,7 +221,11 @@ const getModules = ( indyCredentialFormat: legacyIndyCredentialFormat, }), new DidCommCredentialV2Protocol({ - credentialFormats: [legacyIndyCredentialFormat, jsonLdCredentialFormatService, anonCredsCredentialFormatService], + credentialFormats: [ + legacyIndyCredentialFormat, + jsonLdCredentialFormatService, + anonCredsCredentialFormatService, + ], }), ], }, @@ -220,6 +235,8 @@ const getModules = ( }), questionAnswer: new QuestionAnswerModule(), + // openid4vc: new OpenId4VcModule({}), + // Todo: We can remove this polygon module for time being // polygon: new PolygonModule({ // didContractAddress: didRegistryContractAddress // ? didRegistryContractAddress @@ -230,27 +247,51 @@ const getModules = ( // rpcUrl: rpcUrl ? rpcUrl : (process.env.RPC_URL as string), // serverUrl: fileServerUrl ? fileServerUrl : (process.env.SERVER_URL as string), // }), - openId4VcVerifier: new OpenId4VcVerifierModule({ - baseUrl: - process.env.NODE_ENV === 'PROD' - ? `https://${process.env.APP_URL}/oid4vp` - : `${process.env.AGENT_HTTP_URL}/oid4vp`, - app: openId4VpApp, - authorizationRequestExpirationInSeconds: Number(process.env.OID4VP_AUTH_REQUEST_PROOF_REQUEST_EXPIRY) || 3600, - }), - openId4VcIssuer: new OpenId4VcIssuerModule({ - baseUrl: - process.env.NODE_ENV === 'PROD' - ? `https://${process.env.APP_URL}/oid4vci` - : `${process.env.AGENT_HTTP_URL}/oid4vci`, - app: openId4VcApp, - statefulCredentialOfferExpirationInSeconds: Number(process.env.OID4VCI_CRED_OFFER_EXPIRY) || 3600, - accessTokenExpiresInSeconds: Number(process.env.OID4VCI_ACCESS_TOKEN_EXPIRY) || 3600, - authorizationCodeExpiresInSeconds: Number(process.env.OID4VCI_AUTH_CODE_EXPIRY) || 3600, - cNonceExpiresInSeconds: Number(process.env.OID4VCI_CNONCE_EXPIRY) || 3600, - dpopRequired: false, - credentialRequestToCredentialMapper: (...args) => getCredentialRequestToCredentialMapper()(...args), + openid4vc: new OpenId4VcModule({ + app: expressApp, + issuer: { + baseUrl: + process.env.NODE_ENV === 'PROD' + ? `https://${requireEnv('APP_URL')}/oid4vci` + : `${requireEnv('AGENT_HTTP_URL')}/oid4vci`, + app: expressApp, + statefulCredentialOfferExpirationInSeconds: Number(process.env.OID4VCI_CRED_OFFER_EXPIRY) || 3600, + accessTokenExpiresInSeconds: Number(process.env.OID4VCI_ACCESS_TOKEN_EXPIRY) || 3600, + authorizationCodeExpiresInSeconds: Number(process.env.OID4VCI_AUTH_CODE_EXPIRY) || 3600, + cNonceExpiresInSeconds: Number(process.env.OID4VCI_CNONCE_EXPIRY) || 3600, + dpopRequired: false, + credentialRequestToCredentialMapper: (...args) => getMixedCredentialRequestToCredentialMapper()(...args), + }, + verifier: { + baseUrl: + process.env.NODE_ENV === 'PROD' + ? `https://${requireEnv('APP_URL')}/oid4vp` + : `${requireEnv('AGENT_HTTP_URL')}/oid4vp`, + // app: openId4VpApp, + authorizationRequestExpirationInSeconds: Number(process.env.OID4VP_AUTH_REQUEST_PROOF_REQUEST_EXPIRY) || 3600, + }, }), + // openId4VcVerifier: new OpenId4VcVerifierModule({ + // baseUrl: + // process.env.NODE_ENV === 'PROD' + // ? `https://${process.env.APP_URL}/oid4vp` + // : `${process.env.AGENT_HTTP_URL}/oid4vp`, + // app: openId4VpApp, + // authorizationRequestExpirationInSeconds: Number(process.env.OID4VP_AUTH_REQUEST_PROOF_REQUEST_EXPIRY) || 3600, + // }), + // openId4VcIssuer: new OpenId4VcIssuerModule({ + // baseUrl: + // process.env.NODE_ENV === 'PROD' + // ? `https://${process.env.APP_URL}/oid4vci` + // : `${process.env.AGENT_HTTP_URL}/oid4vci`, + // app: openId4VcApp, + // statefulCredentialOfferExpirationInSeconds: Number(process.env.OID4VCI_CRED_OFFER_EXPIRY) || 3600, + // accessTokenExpiresInSeconds: Number(process.env.OID4VCI_ACCESS_TOKEN_EXPIRY) || 3600, + // authorizationCodeExpiresInSeconds: Number(process.env.OID4VCI_AUTH_CODE_EXPIRY) || 3600, + // cNonceExpiresInSeconds: Number(process.env.OID4VCI_CNONCE_EXPIRY) || 3600, + // dpopRequired: false, + // credentialRequestToCredentialMapper: (...args) => getCredentialRequestToCredentialMapper()(...args), + // }), openId4VcHolderModule: new OpenId4VcHolderModule(), x509: new X509Module({ getTrustedCertificatesForVerification: async (_agentContext, { certificateChain, verification }) => { @@ -275,7 +316,7 @@ const getWithTenantModules = ( autoAcceptCredentials: DidCommAutoAcceptCredential, autoAcceptProofs: DidCommAutoAcceptProof, walletScheme: AskarMultiWalletDatabaseScheme, - walletConfig: AskarModuleConfigStoreOptions + walletConfig: AskarModuleConfigStoreOptions, ) => { const modules = getModules( networkConfig, @@ -288,7 +329,7 @@ const getWithTenantModules = ( autoAcceptCredentials, autoAcceptProofs, walletScheme, - walletConfig + walletConfig, ) return { tenants: new TenantsModule({ @@ -418,7 +459,7 @@ export async function runRestAgent(restConfig: AriesRestConfig) { autoAcceptCredentials || DidCommAutoAcceptCredential.Always, autoAcceptProofs || DidCommAutoAcceptProof.ContentApproved, walletScheme || AskarMultiWalletDatabaseScheme.ProfilePerWallet, - walletConfig + walletConfig, ) const modules = getModules( networkConfig, @@ -431,7 +472,7 @@ export async function runRestAgent(restConfig: AriesRestConfig) { autoAcceptCredentials || DidCommAutoAcceptCredential.Always, autoAcceptProofs || DidCommAutoAcceptProof.ContentApproved, walletScheme || AskarMultiWalletDatabaseScheme.ProfilePerWallet, - walletConfig + walletConfig, ) const agent = new Agent({ config: agentConfig, @@ -468,8 +509,8 @@ export async function runRestAgent(restConfig: AriesRestConfig) { ) transport.app.use(bodyParser.json({ limit: process.env.APP_JSON_BODY_SIZE ?? '5mb' })) - transport.app.use('/oid4vci', modules.openId4VcIssuer.config.app) - transport.app.use('/oid4vp', modules.openId4VcVerifier.config.app) + // transport.app.use('/oid4vci', modules.openid4vc.issuer?.config.app.routes ?? express.Router()) + // transport.app.use('/oid4vp', modules.openid4vc.verifier?.config.app.routes ?? express.Router()) } } @@ -505,6 +546,7 @@ export async function runRestAgent(restConfig: AriesRestConfig) { webhookUrl, port: adminPort, schemaFileServerURL, + app: expressApp, }, apiKey, ) diff --git a/src/controllers/openid4vc/holder/credentialBindingResolver.ts b/src/controllers/openid4vc/holder/credentialBindingResolver.ts new file mode 100644 index 00000000..7f330e73 --- /dev/null +++ b/src/controllers/openid4vc/holder/credentialBindingResolver.ts @@ -0,0 +1,122 @@ +import { DidJwk, DidKey, DidsApi, type JwkDidCreateOptions, type KeyDidCreateOptions, Kms } from '@credo-ts/core' +import { type OpenId4VciCredentialBindingResolver, OpenId4VciCredentialFormatProfile } from '@credo-ts/openid4vc' + +export function getCredentialBindingResolver({ + pidSchemes, + requestBatch, +}: { + pidSchemes?: { sdJwtVcVcts: Array; msoMdocDoctypes: Array } + requestBatch?: boolean | number +}): OpenId4VciCredentialBindingResolver { + return async ({ + supportedDidMethods, + credentialConfiguration, + issuerMaxBatchSize, + proofTypes, + supportsAllDidMethods, + supportsJwk, + credentialFormat, + agentContext, + }) => { + const kms = agentContext.resolve(Kms.KeyManagementApi) + + // First, we try to pick a did method + // Prefer did:jwk, otherwise use did:key, otherwise use undefined + let didMethod: 'key' | 'jwk' | undefined = + supportsAllDidMethods || supportedDidMethods?.includes('did:jwk') + ? 'jwk' + : supportedDidMethods?.includes('did:key') + ? 'key' + : undefined + + // If supportedDidMethods is undefined, and supportsJwk is false, we will default to did:key + // this is important as part of MATTR launchpad support which MUST use did:key but doesn't + // define which did methods they support + if (!supportedDidMethods && !supportsJwk) { + didMethod = 'key' + } + + // We don't want to request more than 10 credentials + const batchSize = + requestBatch === true + ? Math.min(issuerMaxBatchSize, 10) + : typeof requestBatch === 'number' + ? Math.min(issuerMaxBatchSize, requestBatch) + : 1 + // TODO: support key attestations + if (!proofTypes.jwt || proofTypes.jwt.keyAttestationsRequired) { + throw new Error('Unable to request credentials. Only jwt proof type without key attestations supported') + } + + const signatureAlgorithm = proofTypes.jwt?.supportedSignatureAlgorithms?.[0] + + if (!signatureAlgorithm) { + throw new Error('No supported signature algorithms found for JWT proof type') + } + const keys = await Promise.all( + new Array(batchSize).fill(0).map(() => + kms + .createKeyForSignatureAlgorithm({ + algorithm: signatureAlgorithm!, + // FIXME: what should happen with already existing keys created in the secure environment? + backend: 'askar', + }) + .then((key) => Kms.PublicJwk.fromUnknown(key.publicJwk)), + ), + ) + + if (didMethod) { + const dm = didMethod + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const didResults = await Promise.all( + keys.map(async (key) => { + const didResult = await didsApi.create({ + method: dm, + options: { + keyId: key.keyId, + }, + }) + + if (didResult.didState.state !== 'finished') { + throw new Error('DID creation failed.') + } + + let verificationMethodId: string + if (didMethod === 'jwk') { + const didJwk = DidJwk.fromDid(didResult.didState.did) + verificationMethodId = didJwk.verificationMethodId + } else { + const didKey = DidKey.fromDid(didResult.didState.did) + verificationMethodId = `${didKey.did}#${didKey.publicJwk.fingerprint}` + } + + return verificationMethodId + }), + ) + + return { + method: 'did', + didUrls: didResults, + } + } + + // Otherwise we also support plain jwk for sd-jwt only + if ( + supportsJwk && + (credentialFormat === OpenId4VciCredentialFormatProfile.SdJwtVc || + credentialFormat === OpenId4VciCredentialFormatProfile.SdJwtDc || + credentialFormat === OpenId4VciCredentialFormatProfile.MsoMdoc) + ) { + return { + method: 'jwk', + keys, + } + } + + throw new Error( + `No supported binding method could be found. Supported methods are did:key and did:jwk, or plain jwk for sd-jwt/mdoc. Issuer supports ${ + supportsJwk ? 'jwk, ' : '' + }${supportedDidMethods?.join(', ') ?? 'Unknown'}`, + ) + } +} diff --git a/src/controllers/openid4vc/holder/holder.Controller.ts b/src/controllers/openid4vc/holder/holder.Controller.ts index 30f92ad1..8e976112 100644 --- a/src/controllers/openid4vc/holder/holder.Controller.ts +++ b/src/controllers/openid4vc/holder/holder.Controller.ts @@ -1,97 +1,97 @@ -// import { Request as Req } from 'express' -// import { Body, Controller, Get, Post, Route, Security, Tags, Request } from 'tsoa' -// import { injectable } from 'tsyringe' +import { Request as Req } from 'express' +import { Body, Controller, Get, Post, Route, Security, Tags, Request } from 'tsoa' +import { injectable } from 'tsyringe' -// import { SCOPES } from '../../../enums/enum' -// import { -// AuthorizeRequestCredentialOffer, -// RequestCredentialBody, -// ResolveCredentialOfferBody, -// ResolveProofRequest, -// } from '../types/holder.types' +import { SCOPES } from '../../../enums/enum' +import { + AuthorizeRequestCredentialOffer, + RequestCredentialBody, + ResolveCredentialOfferBody, + ResolveProofRequest, +} from '../types/holder.types' -// import { holderService } from './holder.service' +import { holderService } from './holder.service' -// @Tags('oid4vc holders') -// @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) -// @Route('openid4vc/holder') -// @injectable() -// export class HolderController extends Controller { -// /** -// * Get SdJwt type of credentials -// */ -// @Get('/sd-jwt-vcs') -// public async getSdJwtCredentials(@Request() request: Req) { -// return await holderService.getSdJwtCredentials(request) -// } +@Tags('oid4vc holders') +@Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) +@Route('openid4vc/holder') +@injectable() +export class HolderController extends Controller { + /** + * Get SdJwt type of credentials + */ + @Get('/sd-jwt-vcs') + public async getSdJwtCredentials(@Request() request: Req) { + return await holderService.getSdJwtCredentials(request) + } -// /** -// * Fetch all mso mdoc credentials in wallet -// */ -// @Get('/mdoc-vcs') -// public async getMdocCredentials(@Request() request: Req) { -// return await holderService.getMdocCredentials(request) -// } + /** + * Fetch all mso mdoc credentials in wallet + */ + @Get('/mdoc-vcs') + public async getMdocCredentials(@Request() request: Req) { + return await holderService.getMdocCredentials(request) + } -// /** -// * Decode mso mdoc credential in wallet -// */ -// @Post('/mdoc-vcs/decode') -// public async decodeMdocCredential( -// @Request() request: Req, -// @Body() -// body: { -// base64Url: string -// }, -// ) { -// return await holderService.decodeMdocCredential(request, body) -// } + /** + * Decode mso mdoc credential in wallet + */ + @Post('/mdoc-vcs/decode') + public async decodeMdocCredential( + @Request() request: Req, + @Body() + body: { + base64Url: string + }, + ) { + return await holderService.decodeMdocCredential(request, body) + } -// /** -// * Resolve a credential offer -// */ -// @Post('resolve-credential-offer') -// public async resolveCredOffer(@Request() request: Req, @Body() body: ResolveCredentialOfferBody) { -// return await holderService.resolveCredentialOffer(request, body) -// } + /** + * Resolve a credential offer + */ + @Post('resolve-credential-offer') + public async resolveCredOffer(@Request() request: Req, @Body() body: ResolveCredentialOfferBody) { + return await holderService.resolveCredentialOffer(request, body) + } -// // /** -// // * Initiate an OID4VCI authorization request -// // */ -// @Post('authorization-request') -// public async requestAuthorizationForCredential( -// @Request() request: Req, -// @Body() body: AuthorizeRequestCredentialOffer, -// ) { -// return await holderService.requestAuthorizationForCredential(request, body) -// } + // /** + // * Initiate an OID4VCI authorization request + // */ + @Post('authorization-request') + public async requestAuthorizationForCredential( + @Request() request: Req, + @Body() body: AuthorizeRequestCredentialOffer, + ) { + return await holderService.requestAuthorizationForCredential(request, body) + } -// /** -// * Initiates a token request, then requests credentials from issuer -// */ -// @Post('request-credential') -// public async requestCredential(@Request() request: Req, @Body() body: RequestCredentialBody) { -// return await holderService.requestCredential(request, body) -// } + /** + * Initiates a token request, then requests credentials from issuer + */ + @Post('request-credential') + public async requestCredential(@Request() request: Req, @Body() body: RequestCredentialBody) { + return await holderService.requestCredential(request, body) + } -// /** -// * Resolve a proof request -// */ -// @Post('resolve-proof-request') -// public async resolveProofRequest(@Request() request: Req, @Body() body: ResolveProofRequest) { -// return await holderService.resolveProofRequest(request, body) -// } + /** + * Resolve a proof request + */ + @Post('resolve-proof-request') + public async resolveProofRequest(@Request() request: Req, @Body() body: ResolveProofRequest) { + return await holderService.resolveProofRequest(request, body) + } -// /** -// * Accept a proof request -// */ -// @Post('accept-proof-request') -// public async acceptProofRequest(@Request() request: Req, @Body() body: ResolveProofRequest) { -// return await holderService.acceptPresentationRequest(request, body) -// } + /** + * Accept a proof request + */ + @Post('accept-proof-request') + public async acceptProofRequest(@Request() request: Req, @Body() body: ResolveProofRequest) { + return await holderService.acceptPresentationRequest(request, body) + } -// @Post('decode-sdjwt') -// public async decodeSdJwt(@Request() request: Req, @Body() body: { jwt: string }) { -// return await holderService.decodeSdJwt(request, body) -// } -// } \ No newline at end of file + @Post('decode-sdjwt') + public async decodeSdJwt(@Request() request: Req, @Body() body: { jwt: string }) { + return await holderService.decodeSdJwt(request, body) + } +} diff --git a/src/controllers/openid4vc/holder/holder.service.ts b/src/controllers/openid4vc/holder/holder.service.ts index bb2adbc5..1d80b09d 100644 --- a/src/controllers/openid4vc/holder/holder.service.ts +++ b/src/controllers/openid4vc/holder/holder.service.ts @@ -1,280 +1,345 @@ -// import type { -// AuthorizeRequestCredentialOffer, -// RequestCredentialBody, -// ResolveCredentialOfferBody, -// ResolveProofRequest, -// } from '../types/holder.types' -// import type { Agent, DcqlCredentialsForRequest, DcqlQueryResult } from '@credo-ts/core' -// import type { -// OpenId4VcAuthorizationCodeTokenRequestOptions, -// OpenId4VciPreAuthorizedTokenRequestOptions, -// OpenId4VciResolvedCredentialOffer, -// OpenId4VciTokenRequestOptions, -// } from '@credo-ts/openid4vc' +import type { + AuthorizeRequestCredentialOffer, + RequestCredentialBody, + ResolveCredentialOfferBody, + ResolveProofRequest, +} from '../types/holder.types' +import type { Agent, DcqlCredentialsForRequest, DcqlQueryResult } from '@credo-ts/core' +import type { + OpenId4VcAuthorizationCodeTokenRequestOptions, + OpenId4VciPreAuthorizedTokenRequestOptions, + OpenId4VciResolvedCredentialOffer, + OpenId4VciTokenRequestOptions, +} from '@credo-ts/openid4vc' +import type { Request as Req } from 'express' -// import { -// DifPresentationExchangeService, -// DidKey, -// DidJwk, -// // getJwkFromKey, -// Mdoc, -// W3cJsonLdVerifiableCredential, -// W3cJwtVerifiableCredential, -// } from '@credo-ts/core' -// import { -// OpenId4VciAuthorizationFlow, -// authorizationCodeGrantIdentifier, -// preAuthorizedCodeGrantIdentifier, -// } from '@credo-ts/openid4vc' -// import { Request as Req } from 'express' -// export class HolderService { -// private HOLDER_REDIRECT = process.env.HOLDER_REDIRECT ?? 'http://localhost:4001/redirect' -// private HOLDER_CLIENT_ID = process.env.HOLDER_CLIENT_ID ?? 'wallet' +import { + DifPresentationExchangeService, + DidKey, + DidJwk, + Mdoc, + W3cJsonLdVerifiableCredential, + W3cJwtVerifiableCredential, + SdJwtVcRecord, + MdocRecord, +} from '@credo-ts/core' +import { + OpenId4VciAuthorizationFlow, + authorizationCodeGrantIdentifier, + preAuthorizedCodeGrantIdentifier, +} from '@credo-ts/openid4vc' -// public async getSdJwtCredentials(agentReq: Req) { -// return await agentReq.agent.sdJwtVc.getAll() -// } +import { getCredentialBindingResolver } from './credentialBindingResolver' +export class HolderService { + private HOLDER_REDIRECT = process.env.HOLDER_REDIRECT ?? 'http://localhost:4001/redirect' + private HOLDER_CLIENT_ID = process.env.HOLDER_CLIENT_ID ?? 'wallet' -// public async getMdocCredentials(agentReq: Req) { -// return await agentReq.agent.mdoc.getAll() -// } + public async getSdJwtCredentials(agentReq: Req) { + return await agentReq.agent.sdJwtVc.getAll() + } -// public async decodeMdocCredential( -// agentReq: Req, -// options: { -// base64Url: string -// }, -// ) { -// const credential = Mdoc.fromBase64Url(options.base64Url) -// return { -// namespace: credential.issuerSignedNamespaces, -// docType: credential.docType, -// validityInfo: credential.validityInfo, -// issuerSignedCertificateChain: credential.issuerSignedCertificateChain, -// } as any -// } + public async getMdocCredentials(agentReq: Req) { + return await agentReq.agent.mdoc.getAll() + } -// public async resolveCredentialOffer(agentReq: Req, body: ResolveCredentialOfferBody) { -// return (await agentReq.agent.modules.openId4VcHolderModule.resolveCredentialOffer(body.credentialOfferUri)) as any -// } + public async decodeMdocCredential( + agentReq: Req, + options: { + base64Url: string + }, + ) { + const credential = Mdoc.fromBase64Url(options.base64Url) + return { + namespace: credential.issuerSignedNamespaces, + docType: credential.docType, + validityInfo: credential.validityInfo, + issuerSignedCertificateChain: credential.issuerSignedCertificateChain, + } as any + } -// public async requestAuthorizationForCredential(agentReq: Req, body: AuthorizeRequestCredentialOffer) { -// console.log('Requesting authorization for credential offer:', body) -// const resolvedCredentialOffer = await agentReq.agent.modules.openId4VcHolderModule.resolveCredentialOffer( -// body.credentialOfferUri, -// ) -// console.log('Resolved credential offer:', resolvedCredentialOffer) -// const resolvedAuthorization = await this.initiateAuthorization( -// agentReq, -// resolvedCredentialOffer, -// body.credentialsToRequest, -// ) + public async resolveCredentialOffer(agentReq: Req, body: ResolveCredentialOfferBody) { + return (await agentReq.agent.modules.openid4vc.holder.resolveCredentialOffer(body.credentialOfferUri)) as any + } -// let actionToTake = '' -// let authorizationRequestUrl: string | undefined = undefined -// let codeVerifier: string | undefined = undefined -// console.log('Resolved authorization', resolvedAuthorization) + public async requestAuthorizationForCredential(agentReq: Req, body: AuthorizeRequestCredentialOffer) { + const resolvedCredentialOffer = await agentReq.agent.modules.openid4vc.holder.resolveCredentialOffer( + body.credentialOfferUri, + ) + const resolvedAuthorization = await this.initiateAuthorization( + agentReq, + resolvedCredentialOffer, + body.credentialsToRequest, + ) -// switch (resolvedAuthorization.authorizationFlow) { -// case 'Oauth2Redirect': -// actionToTake = 'Open the authorizationRequestUrl in your browser.' -// authorizationRequestUrl = resolvedAuthorization.authorizationRequestUrl -// codeVerifier = resolvedAuthorization.codeVerifier -// break -// case 'PresentationDuringIssuance': -// actionToTake = 'Presentation during issuance not supported yet' -// break -// case 'PreAuthorized': -// if (resolvedCredentialOffer.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]?.tx_code) { -// actionToTake = 'Ask for txcode from issuer and use it further' -// } -// break -// } + let actionToTake = '' + let authorizationRequestUrl: string | undefined = undefined + let codeVerifier: string | undefined = undefined -// return { actionToTake, authorizationRequestUrl, codeVerifier } as any -// } + switch (resolvedAuthorization.authorizationFlow) { + case 'Oauth2Redirect': + actionToTake = 'Open the authorizationRequestUrl in your browser.' + authorizationRequestUrl = resolvedAuthorization.authorizationRequestUrl + codeVerifier = resolvedAuthorization.codeVerifier + break + case 'PresentationDuringIssuance': + actionToTake = 'Presentation during issuance not supported yet' + break + case 'PreAuthorized': + if (resolvedCredentialOffer.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]?.tx_code) { + actionToTake = 'Ask for txcode from issuer and use it further' + } + break + } -// public async requestCredential(agentReq: Req, body: RequestCredentialBody) { -// const resolvedCredentialOffer = await agentReq.agent.modules.openId4VcHolderModule.resolveCredentialOffer( -// body.credentialOfferUri, -// ) + return { actionToTake, authorizationRequestUrl, codeVerifier } as any + } -// let options: OpenId4VciTokenRequestOptions -// if (resolvedCredentialOffer.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]) { -// options = { -// resolvedCredentialOffer, -// txCode: body.txCode, -// code: body.authorizationCode, -// } as OpenId4VciPreAuthorizedTokenRequestOptions -// } else { -// options = { -// resolvedCredentialOffer, -// code: body.authorizationCode, -// clientId: this.HOLDER_CLIENT_ID, -// codeVerifier: body.codeVerifier, -// redirectUri: this.HOLDER_REDIRECT, -// } as OpenId4VcAuthorizationCodeTokenRequestOptions -// } + public async requestCredential(agentReq: Req, body: RequestCredentialBody) { + const resolvedCredentialOffer = await agentReq.agent.modules.openid4vc.holder.resolveCredentialOffer( + body.credentialOfferUri, + ) -// // return (await this.requestAndStoreCredentials(agentReq, resolvedCredentialOffer, options)) as any -// return {} as any -// } + let options: OpenId4VciTokenRequestOptions + if (resolvedCredentialOffer.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]) { + options = { + resolvedCredentialOffer, + txCode: body.txCode, + code: body.authorizationCode, + } as OpenId4VciPreAuthorizedTokenRequestOptions + } else { + options = { + resolvedCredentialOffer, + code: body.authorizationCode, + clientId: this.HOLDER_CLIENT_ID, + codeVerifier: body.codeVerifier, + redirectUri: this.HOLDER_REDIRECT, + } as OpenId4VcAuthorizationCodeTokenRequestOptions + } -// // private async requestAndStoreCredentials( -// // agentReq: Req, -// // resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, -// // options: OpenId4VciTokenRequestOptions, -// // ) { -// // const tokenResponse = await agentReq.agent.modules.openId4VcHolderModule.requestToken({ ...options }) -// // const credentialResponse = await agentReq.agent.modules.openId4VcHolderModule.requestCredentials({ -// // ...options, -// // credentialConfigurationIds: resolvedCredentialOffer.credentialOfferPayload.credential_configuration_ids, -// // credentialBindingResolver: async ({ -// // keyTypes, -// // supportedDidMethods, -// // supportsAllDidMethods, -// // }: { -// // keyTypes: string[] -// // supportedDidMethods?: string[] -// // supportsAllDidMethods?: boolean -// // }) => { -// // const key = await agentReq.agent.wallet.createKey({ keyType: keyTypes[0] as any }) -// // if (supportsAllDidMethods || supportedDidMethods?.includes('did:key')) { -// // const didKey = new DidKey(key) -// // return { method: 'did', didUrl: `${didKey.did}#${didKey.key.fingerprint}` } -// // } -// // if (supportedDidMethods?.includes('did:jwk')) { -// // const didJwk = DidJwk.fromJwk(getJwkFromKey(key)) -// // return { method: 'did', didUrl: `${didJwk.did}#0` } -// // } -// // return { method: 'jwk', jwk: getJwkFromKey(key) } -// // }, -// // ...tokenResponse, -// // }) + return (await this.requestAndStoreCredentials(agentReq, resolvedCredentialOffer, options)) as any + // return {} as any + } + // private async requestAndStoreCredentials( + // // agentReq: Req, + // // resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, + // // options: OpenId4VciTokenRequestOptions, + // // ) { + // // const tokenResponse = await agentReq.agent.modules.openId4VcHolderModule.requestToken({ ...options }) + // // const credentialResponse = await agentReq.agent.modules.openId4VcHolderModule.requestCredentials({ + // // ...options, + // // credentialConfigurationIds: resolvedCredentialOffer.credentialOfferPayload.credential_configuration_ids, + // // credentialBindingResolver: async ({ + // // keyTypes, + // // supportedDidMethods, + // // supportsAllDidMethods, + // // }: { + // // keyTypes: string[] + // // supportedDidMethods?: string[] + // // supportsAllDidMethods?: boolean + // // }) => { + // // const key = await agentReq.agent.wallet.createKey({ keyType: keyTypes[0] as any }) + // // if (supportsAllDidMethods || supportedDidMethods?.includes('did:key')) { + // // const didKey = new DidKey(key) + // // return { method: 'did', didUrl: `${didKey.did}#${didKey.key.fingerprint}` } + // // } + // // if (supportedDidMethods?.includes('did:jwk')) { + // // const didJwk = DidJwk.fromJwk(getJwkFromKey(key)) + // // return { method: 'did', didUrl: `${didJwk.did}#0` } + // // } + // // return { method: 'jwk', jwk: getJwkFromKey(key) } + // // }, + // // ...tokenResponse, + // // }) -// // const storedCredentials = await Promise.all( -// // credentialResponse.credentials.map(async (response: any) => { -// // const credential = response.credentials[0] -// // if (credential instanceof W3cJwtVerifiableCredential || credential instanceof W3cJsonLdVerifiableCredential) { -// // return await agentReq.agent.w3cCredentials.storeCredential({ credential }) -// // } -// // if (credential instanceof Mdoc) { -// // return await agentReq.agent.mdoc.store(credential) -// // } -// // return await agentReq.agent.sdJwtVc.store(credential.compact) -// // }), -// // ) + // // const storedCredentials = await Promise.all( + // // credentialResponse.credentials.map(async (response: any) => { + // // const credential = response.credentials[0] + // // if (credential instanceof W3cJwtVerifiableCredential || credential instanceof W3cJsonLdVerifiableCredential) { + // // return await agentReq.agent.w3cCredentials.storeCredential({ credential }) + // // } + // // if (credential instanceof Mdoc) { + // // return await agentReq.agent.mdoc.store(credential) + // // } + // // return await agentReq.agent.sdJwtVc.store(credential.compact) + // // }), + // // ) -// // return storedCredentials as any -// // } + // // return storedCredentials as any + // // } + private async requestAndStoreCredentials( + agentReq: Req, + resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, + options: OpenId4VciTokenRequestOptions, + ) { + const tokenResponse = await agentReq.agent.modules.openid4vc.holder.requestToken({ ...options }) + const credentialResponse = await agentReq.agent.modules.openid4vc.holder.requestCredentials({ + ...options, + credentialConfigurationIds: resolvedCredentialOffer.credentialOfferPayload.credential_configuration_ids, + // credentialBindingResolver: async ({ + // keyTypes, + // supportedDidMethods, + // supportsAllDidMethods, + // }: { + // keyTypes: string[] + // supportedDidMethods?: string[] + // supportsAllDidMethods?: boolean + // }) => { + // const key = await agentReq.agent.wallet.createKey({ keyType: keyTypes[0] as any }) + // if (supportsAllDidMethods || supportedDidMethods?.includes('did:key')) { + // const didKey = new DidKey(key) + // return { method: 'did', didUrl: `${didKey.did}#${didKey.key.fingerprint}` } + // } + // if (supportedDidMethods?.includes('did:jwk')) { + // const didJwk = DidJwk.fromJwk(getJwkFromKey(key)) + // return { method: 'did', didUrl: `${didJwk.did}#0` } + // } + // return { method: 'jwk', jwk: getJwkFromKey(key) } + // }, + credentialBindingResolver: getCredentialBindingResolver({ + pidSchemes: undefined, + requestBatch: false, + }), + ...tokenResponse, + }) + // const credentialResponse = await agentReq.agent.modules.openid4vc.holder.requestCredentials({ + // ...options, + // credentialConfigurationIds: resolvedCredentialOffer.credentialOfferPayload.credential_configuration_ids, + // credentialBindingResolver: async (options) => { + // const { keyId, publicJwk } = await agentReq.agent.kms.createKey({ + // type: { + // crv: 'Ed25519', + // kty: 'OKP', + // }, + // }) + // if (options.supportsAllDidMethods || options.supportedDidMethods?.includes('did:key')) { + // const didKey = new DidKey(key) + // return { method: 'did', didUrls: [`${didKey.did}#${didKey.key.fingerprint}`] } + // } + // if (options.supportedDidMethods?.includes('did:jwk')) { + // const didJwk = DidJwk.fromKey(key) + // return { method: 'did', didUrls: [`${didJwk.did}#0`] } + // } + // return { method: 'jwk', jwk: key } + // }, + // }) -// private async initiateAuthorization( -// agentReq: Req, -// resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, -// credentialsToRequest: string[], -// ) { -// console.log('Initiating authorization with resolvedCredentialOffer:', resolvedCredentialOffer) -// console.log('Credentials to request:', credentialsToRequest) + const storedCredentials = await Promise.all( + credentialResponse.credentials.map(async (response) => { + const credentialRecord = response.record + // if (credential instanceof W3cJwtVerifiableCredential || credential instanceof W3cJsonLdVerifiableCredential) { + // return await agentReq.agent.w3cCredentials.storeCredential({ credential }) + // } + if (credentialRecord instanceof MdocRecord) { + return await agentReq.agent.mdoc.store({ record: credentialRecord }) + } + if (credentialRecord instanceof SdJwtVcRecord) { + return await agentReq.agent.sdJwtVc.store({ + record: credentialRecord, + }) + } + throw new Error(`Unsupported credential record type`) + }), + ) -// const grants = resolvedCredentialOffer.credentialOfferPayload.grants -// console.log('Grants:', grants) + return storedCredentials as any + } -// // 👉 Handle Pre-Authorized Code Grant -// if (grants?.[preAuthorizedCodeGrantIdentifier]) { -// const preAuthorizedCode = grants[preAuthorizedCodeGrantIdentifier]['pre-authorized_code'] -// return { -// authorizationFlow: 'PreAuthorized' as const, -// preAuthorizedCode, -// } -// } + private async initiateAuthorization( + agentReq: Req, + resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, + credentialsToRequest: string[], + ) { + const grants = resolvedCredentialOffer.credentialOfferPayload.grants -// // 👉 Handle Authorization Code Grant -// if (grants?.[authorizationCodeGrantIdentifier]) { -// console.log('Using authorization code grant flow') + // 👉 Handle Pre-Authorized Code Grant + if (grants?.[preAuthorizedCodeGrantIdentifier]) { + const preAuthorizedCode = grants[preAuthorizedCodeGrantIdentifier]['pre-authorized_code'] + return { + authorizationFlow: 'PreAuthorized' as const, + preAuthorizedCode, + } + } -// const scope = Object.entries(resolvedCredentialOffer.offeredCredentialConfigurations) -// .map(([id, val]) => (credentialsToRequest.includes(id) ? val.scope : undefined)) -// .filter((v): v is string => Boolean(v)) + // 👉 Handle Authorization Code Grant + if (grants?.[authorizationCodeGrantIdentifier]) { + const scope = Object.entries(resolvedCredentialOffer.offeredCredentialConfigurations) + .map(([id, val]) => (credentialsToRequest.includes(id) ? val.scope : undefined)) + .filter((v): v is string => Boolean(v)) -// const resolved = await agentReq.agent.modules.openId4VcHolderModule.resolveOpenId4VciAuthorizationRequest( -// resolvedCredentialOffer, -// { -// clientId: this.HOLDER_CLIENT_ID, -// redirectUri: this.HOLDER_REDIRECT, -// scope, -// }, -// ) + const resolved = await agentReq.agent.modules.openid4vc.holder.resolveOpenId4VciAuthorizationRequest( + resolvedCredentialOffer, + { + clientId: this.HOLDER_CLIENT_ID, + redirectUri: this.HOLDER_REDIRECT, + scope, + }, + ) -// // 👉 Support Presentation During Issuance flow -// if (resolved.authorizationFlow === OpenId4VciAuthorizationFlow.PresentationDuringIssuance) { -// return { -// ...resolved, -// authorizationFlow: 'PresentationDuringIssuance' as const, -// } -// } + // 👉 Support Presentation During Issuance flow + if (resolved.authorizationFlow === OpenId4VciAuthorizationFlow.PresentationDuringIssuance) { + return { + ...resolved, + authorizationFlow: 'PresentationDuringIssuance' as const, + } + } -// return { -// ...resolved, -// authorizationFlow: 'Oauth2Redirect' as const, -// } as any -// } + return { + ...resolved, + authorizationFlow: 'Oauth2Redirect' as const, + } as any + } -// // ❌ Unsupported grant -// throw new Error('Unsupported grant type') -// } + // ❌ Unsupported grant + throw new Error('Unsupported grant type') + } -// public async resolveProofRequest(agentReq: Req, body: ResolveProofRequest) { -// return (await agentReq.agent.modules.openId4VcHolderModule.resolveOpenId4VpAuthorizationRequest( -// body.proofRequestUri, -// )) as any -// } + public async resolveProofRequest(agentReq: Req, body: ResolveProofRequest) { + return (await agentReq.agent.modules.openid4vc.holder.resolveOpenId4VpAuthorizationRequest( + body.proofRequestUri, + )) as any + } -// public async acceptPresentationRequest(agentReq: Req, body: ResolveProofRequest) { -// const resolved = await agentReq.agent.modules.openId4VcHolderModule.resolveOpenId4VpAuthorizationRequest( -// body.proofRequestUri, -// ) -// console.log('Resolved proof request:', resolved) -// // const presentationExchangeService = agent.dependencyManager.resolve(DifPresentationExchangeService) + public async acceptPresentationRequest(agentReq: Req, body: ResolveProofRequest) { + const resolved = await agentReq.agent.modules.openid4vc.holder.resolveOpenId4VpAuthorizationRequest( + body.proofRequestUri, + ) + // const presentationExchangeService = agent.dependencyManager.resolve(DifPresentationExchangeService) -// if (!resolved.dcql) throw new Error('Missing DCQL on request') -// console.log('DCQL query result:', resolved.dcql.queryResult) -// // -// let dcqlCredentials -// try { -// dcqlCredentials = await agentReq.agent.modules.openId4VcHolderModule.selectCredentialsForDcqlRequest( -// resolved.dcql.queryResult, -// ) -// console.log('Selected credentials for DCQL request:', dcqlCredentials) -// } catch (error) { -// console.error('Error selecting credentials for DCQL request:', error) -// throw error -// } -// const submissionResult = await agentReq.agent.modules.openId4VcHolderModule.acceptOpenId4VpAuthorizationRequest({ -// authorizationRequestPayload: resolved.authorizationRequestPayload, -// dcql: { -// credentials: dcqlCredentials as DcqlCredentialsForRequest, -// }, -// }) -// console.log('Presentation submission result:', submissionResult) -// return submissionResult.serverResponse -// } + if (!resolved.dcql) throw new Error('Missing DCQL on request') + // + let dcqlCredentials + try { + dcqlCredentials = await agentReq.agent.modules.openid4vc.holder.selectCredentialsForDcqlRequest( + resolved.dcql.queryResult, + ) + } catch (error) { + throw error + } + const submissionResult = await agentReq.agent.modules.openid4vc.holder.acceptOpenId4VpAuthorizationRequest({ + authorizationRequestPayload: resolved.authorizationRequestPayload, + dcql: { + credentials: dcqlCredentials as DcqlCredentialsForRequest, + }, + }) + return submissionResult.serverResponse + } -// public async decodeSdJwt(agentReq: Req, body: { jwt: string }) { -// const sdJwt = agentReq.agent.sdJwtVc.fromCompact(body.jwt) -// return sdJwt as any -// } + public async decodeSdJwt(agentReq: Req, body: { jwt: string }) { + const sdJwt = agentReq.agent.sdJwtVc.fromCompact(body.jwt) + return sdJwt as any + } -// public async getSelectedCredentialsForRequest( -// dcqlQueryResult: DcqlQueryResult, -// selectedCredentials: { [credentialQueryId: string]: string }, -// ) { -// if (!dcqlQueryResult.can_be_satisfied) { -// throw new Error( -// 'Cannot select the credentials for the dcql query presentation if the request cannot be satisfied', -// ) -// } -// // TODO: Implement logic to select credentials based on selectedCredentials -// return {} as any // Placeholder return to avoid errors -// } -// } -// export const holderService = new HolderService() + public async getSelectedCredentialsForRequest( + dcqlQueryResult: DcqlQueryResult, + selectedCredentials: { [credentialQueryId: string]: string }, + ) { + if (!dcqlQueryResult.can_be_satisfied) { + throw new Error( + 'Cannot select the credentials for the dcql query presentation if the request cannot be satisfied', + ) + } + // TODO: Implement logic to select credentials based on selectedCredentials + return {} as any // Placeholder return to avoid errors + } +} +export const holderService = new HolderService() diff --git a/src/controllers/openid4vc/issuance-sessions/issuance-sessions.Controller.ts b/src/controllers/openid4vc/issuance-sessions/issuance-sessions.Controller.ts index f49e3d0f..792e58b4 100644 --- a/src/controllers/openid4vc/issuance-sessions/issuance-sessions.Controller.ts +++ b/src/controllers/openid4vc/issuance-sessions/issuance-sessions.Controller.ts @@ -1,107 +1,107 @@ -// import { OpenId4VcIssuanceSessionState } from '@credo-ts/openid4vc' -// import { Request as Req } from 'express' -// import { Body, Controller, Delete, Get, Path, Post, Put, Query, Request, Route, Tags, Security } from 'tsoa' -// import { injectable } from 'tsyringe' +import { OpenId4VcIssuanceSessionState } from '@credo-ts/openid4vc' +import { Request as Req } from 'express' +import { Body, Controller, Delete, Get, Path, Post, Put, Query, Request, Route, Tags, Security } from 'tsoa' +import { injectable } from 'tsyringe' -// import { SCOPES } from '../../../enums' -// // eslint-disable-next-line import/order -// import ErrorHandlingService from '../../../errorHandlingService' +import { SCOPES } from '../../../enums' +// eslint-disable-next-line import/order +import ErrorHandlingService from '../../../errorHandlingService' -// // import { AgentWithRootOrTenant } from '../../types/agent' -// import { OpenId4VcIssuanceSessionsCreateOffer } from '../types/issuer.types' +// import { AgentWithRootOrTenant } from '../../types/agent' +import { OpenId4VcIssuanceSessionsCreateOffer } from '../types/issuer.types' -// import { issuanceSessionService } from './issuance-sessions.service' +import { issuanceSessionService } from './issuance-sessions.service' -// /** -// * Controller for managing OpenID4VC issuance sessions. -// * Provides endpoints to create credential offers, retrieve issuance sessions, -// * update session metadata, and delete sessions. -// */ -// @Tags('oid4vc issuance sessions') -// @Route('/openid4vc/issuance-sessions') -// @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) -// @injectable() -// export class IssuanceSessionsController extends Controller { -// /** -// * Creates a credential offer with the specified credential configurations and authorization type. -// */ -// @Post('/create-credential-offer') -// public async createCredentialOffer(@Request() request: Req, @Body() options: OpenId4VcIssuanceSessionsCreateOffer) { -// try { -// return await issuanceSessionService.createCredentialOffer(options, request) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } +/** + * Controller for managing OpenID4VC issuance sessions. + * Provides endpoints to create credential offers, retrieve issuance sessions, + * update session metadata, and delete sessions. + */ +@Tags('oid4vc issuance sessions') +@Route('/openid4vc/issuance-sessions') +@Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) +@injectable() +export class IssuanceSessionsController extends Controller { + /** + * Creates a credential offer with the specified credential configurations and authorization type. + */ + @Post('/create-credential-offer') + public async createCredentialOffer(@Request() request: Req, @Body() options: OpenId4VcIssuanceSessionsCreateOffer) { + try { + return await issuanceSessionService.createCredentialOffer(options, request) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Get issuance details by issuance SessionId -// */ -// @Get('/:issuanceSessionId') -// public async getIssuanceSessionsById(@Request() request: Req, @Path('issuanceSessionId') issuanceSessionId: string) { -// try { -// return await issuanceSessionService.getIssuanceSessionsById(request, issuanceSessionId) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } + /** + * Get issuance details by issuance SessionId + */ + @Get('/:issuanceSessionId') + public async getIssuanceSessionsById(@Request() request: Req, @Path('issuanceSessionId') issuanceSessionId: string) { + try { + return await issuanceSessionService.getIssuanceSessionsById(request, issuanceSessionId) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Fetch all issuance sessions by query -// */ -// @Get('/') -// public async getIssuanceSessionsByQuery( -// @Request() request: Req, -// @Query() cNonce?: string, -// @Query() publicIssuerId?: string, -// @Query() preAuthorizedCode?: string, -// @Query() state?: OpenId4VcIssuanceSessionState, -// @Query() credentialOfferUri?: string, -// @Query() authorizationCode?: string, -// ) { -// try { -// return await issuanceSessionService.getIssuanceSessionsByQuery( -// request, -// cNonce, -// publicIssuerId, -// preAuthorizedCode, -// state, -// credentialOfferUri, -// authorizationCode, -// ) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } + /** + * Fetch all issuance sessions by query + */ + @Get('/') + public async getIssuanceSessionsByQuery( + @Request() request: Req, + @Query() cNonce?: string, + @Query() publicIssuerId?: string, + @Query() preAuthorizedCode?: string, + @Query() state?: OpenId4VcIssuanceSessionState, + @Query() credentialOfferUri?: string, + @Query() authorizationCode?: string, + ) { + try { + return await issuanceSessionService.getIssuanceSessionsByQuery( + request, + cNonce, + publicIssuerId, + preAuthorizedCode, + state, + credentialOfferUri, + authorizationCode, + ) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Update issuance session metadata by session ID -// */ -// @Put('/:issuanceSessionId') -// public async updateSessionById( -// @Request() request: Req, -// @Path('issuanceSessionId') issuanceSessionId: string, -// @Body() metadata: Record, -// ) { -// try { -// return await issuanceSessionService.updateSessionIssuanceMetadataById(request, issuanceSessionId, metadata) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } + /** + * Update issuance session metadata by session ID + */ + @Put('/:issuanceSessionId') + public async updateSessionById( + @Request() request: Req, + @Path('issuanceSessionId') issuanceSessionId: string, + @Body() metadata: Record, + ) { + try { + return await issuanceSessionService.updateSessionIssuanceMetadataById(request, issuanceSessionId, metadata) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Delete issuance session by session ID -// */ -// @Delete('/:issuanceSessionId') -// public async deleteIssuanceSessionById( -// @Request() request: Req, -// @Path('issuanceSessionId') issuanceSessionId: string, -// ) { -// try { -// await issuanceSessionService.deleteById(request, issuanceSessionId) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } -// } + /** + * Delete issuance session by session ID + */ + @Delete('/:issuanceSessionId') + public async deleteIssuanceSessionById( + @Request() request: Req, + @Path('issuanceSessionId') issuanceSessionId: string, + ) { + try { + await issuanceSessionService.deleteById(request, issuanceSessionId) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } +} diff --git a/src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts b/src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts index ffd2cf17..8dd7027b 100644 --- a/src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts +++ b/src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts @@ -1,138 +1,143 @@ -// import type { OpenId4VcIssuanceSessionsCreateOffer } from '../types/issuer.types' -// import type { Request as Req } from 'express' - -// import { type OpenId4VcIssuanceSessionState } from '@credo-ts/openid4vc' -// import { OpenId4VcIssuanceSessionRepository } from '@credo-ts/openid4vc' - -// import { SignerMethod } from '../../../enums/enum' -// import { BadRequestError, NotFoundError } from '../../../errors/errors' - -// class IssuanceSessionsService { -// public async createCredentialOffer(options: OpenId4VcIssuanceSessionsCreateOffer, agentReq: Req) { -// const { credentials, publicIssuerId } = options - -// const issuer = await agentReq.agent.modules.openId4VcIssuer.getIssuerByIssuerId(publicIssuerId) - -// const mappedCredentials = credentials.map((cred) => { -// const supported = issuer.credentialConfigurationsSupported[cred.credentialSupportedId] -// if (!supported) { -// throw new Error(`CredentialSupportedId '${cred.credentialSupportedId}' is not supported by issuer`) -// } -// if (supported.format !== cred.format) { -// throw new Error( -// `Format mismatch for '${cred.credentialSupportedId}': expected '${supported.format}', got '${cred.format}'`, -// ) -// } - -// // must have signing options -// if (!cred.signerOptions?.method) { -// throw new BadRequestError( -// `signerOptions must be provided and allowed methods are ${Object.values(SignerMethod).join(', ')}`, -// ) -// } - -// if (cred.signerOptions.method == SignerMethod.Did && !cred.signerOptions.did) { -// throw new BadRequestError( -// `For ${cred.credentialSupportedId} : did must be present inside signerOptions if SignerMethod is 'did' `, -// ) -// } - -// if (cred.signerOptions.method === SignerMethod.X5c && !cred.signerOptions.x5c) { -// throw new BadRequestError( -// `For ${cred.credentialSupportedId} : x5c must be present inside signerOptions if SignerMethod is 'x5c' `, -// ) -// } - -// const currentVct = cred.payload && 'vct' in cred.payload ? cred.payload.vct : undefined -// return { -// ...cred, -// payload: { -// ...cred.payload, -// vct: currentVct ?? (typeof supported.vct === 'string' ? supported.vct : undefined), -// }, -// } -// }) - -// options.issuanceMetadata ||= {} - -// options.issuanceMetadata.credentials = mappedCredentials - -// const { credentialOffer, issuanceSession } = await agentReq.agent.modules.openId4VcIssuer.createCredentialOffer({ -// issuerId: publicIssuerId, -// issuanceMetadata: options.issuanceMetadata, -// offeredCredentials: credentials.map((c) => c.credentialSupportedId), -// preAuthorizedCodeFlowConfig: options.preAuthorizedCodeFlowConfig, -// authorizationCodeFlowConfig: options.authorizationCodeFlowConfig, -// }) - -// return { credentialOffer, issuanceSession } -// } - -// public async getIssuanceSessionsById(agentReq: Req, sessionId: string) { -// return agentReq.agent.modules.openId4VcIssuer.getIssuanceSessionById(sessionId) -// } - -// public async getIssuanceSessionsByQuery( -// agentReq: Req, -// cNonce?: string, -// publicIssuerId?: string, -// preAuthorizedCode?: string, -// state?: OpenId4VcIssuanceSessionState, -// credentialOfferUri?: string, -// authorizationCode?: string, -// ) { -// const issuanceSessionRepository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) -// const issuanceSessions = await issuanceSessionRepository.findByQuery(agentReq.agent.context, { -// cNonce, -// issuerId: publicIssuerId, -// preAuthorizedCode, -// state, -// credentialOfferUri, -// authorizationCode, -// }) - -// return issuanceSessions -// } - -// /** -// * update an existing issuance session metadata, useful for mobile edge -// * agents that will scan QR codes to notify the system of their -// * wallet user id -// * -// * @param issuerAgent -// * @param sessionId -// * @param metadata -// * @returns the updated issuance session record -// */ -// public async updateSessionIssuanceMetadataById(agentReq: Req, sessionId: string, metadata: Record) { -// const issuanceSessionRepository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - -// const record = await issuanceSessionRepository.findById(agentReq.agent.context, sessionId) - -// if (!record) { -// throw new NotFoundError(`Issuance session with id ${sessionId} not found`) -// } - -// record.issuanceMetadata = { -// ...record.issuanceMetadata, -// ...metadata, -// } - -// await issuanceSessionRepository.update(agentReq.agent.context, record) - -// return record -// } - -// /** -// * deletes ann issuance session by id -// * -// * @param sessionId -// * @param issuerAgent -// */ -// public async deleteById(agentReq: Req, sessionId: string): Promise { -// const issuanceSessionRepository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) -// await issuanceSessionRepository.deleteById(agentReq.agent.context, sessionId) -// } -// } - -// export const issuanceSessionService = new IssuanceSessionsService() +import type { OpenId4VcIssuanceSessionsCreateOffer } from '../types/issuer.types' +import type { Request as Req } from 'express' + +import { type OpenId4VcIssuanceSessionState } from '@credo-ts/openid4vc' +import { OpenId4VcIssuanceSessionRepository } from '@credo-ts/openid4vc' + +import { SignerMethod } from '../../../enums/enum' +import { BadRequestError, NotFoundError } from '../../../errors/errors' + +class IssuanceSessionsService { + public async createCredentialOffer(options: OpenId4VcIssuanceSessionsCreateOffer, agentReq: Req) { + const { credentials, publicIssuerId } = options + + const issuer = await agentReq.agent.modules.openid4vc.issuer?.getIssuerByIssuerId(publicIssuerId) + + const mappedCredentials = credentials.map((cred) => { + const supported = issuer?.credentialConfigurationsSupported[cred.credentialSupportedId] + if (!supported) { + throw new Error(`CredentialSupportedId '${cred.credentialSupportedId}' is not supported by issuer`) + } + if (supported.format !== cred.format) { + throw new Error( + `Format mismatch for '${cred.credentialSupportedId}': expected '${supported.format}', got '${cred.format}'`, + ) + } + + // must have signing options + if (!cred.signerOptions?.method) { + throw new BadRequestError( + `signerOptions must be provided and allowed methods are ${Object.values(SignerMethod).join(', ')}`, + ) + } + + if (cred.signerOptions.method == SignerMethod.Did && !cred.signerOptions.did) { + throw new BadRequestError( + `For ${cred.credentialSupportedId} : did must be present inside signerOptions if SignerMethod is 'did' `, + ) + } + + if (cred.signerOptions.method === SignerMethod.X5c && !cred.signerOptions.x5c) { + throw new BadRequestError( + `For ${cred.credentialSupportedId} : x5c must be present inside signerOptions if SignerMethod is 'x5c' `, + ) + } + + const currentVct = cred.payload && 'vct' in cred.payload ? cred.payload.vct : undefined + return { + ...cred, + payload: { + ...cred.payload, + vct: currentVct ?? (typeof supported.vct === 'string' ? supported.vct : undefined), + }, + } + }) + + options.issuanceMetadata ||= {} + + options.issuanceMetadata.credentials = mappedCredentials + + const issuerModule = agentReq.agent.modules.openid4vc.issuer + + if (!issuerModule) { + throw new Error('OID4VC issuer module not initialized') + } + const { credentialOffer, issuanceSession } = await issuerModule.createCredentialOffer({ + issuerId: publicIssuerId, + issuanceMetadata: options.issuanceMetadata, + credentialConfigurationIds: credentials.map((c) => c.credentialSupportedId), + preAuthorizedCodeFlowConfig: options.preAuthorizedCodeFlowConfig, + authorizationCodeFlowConfig: options.authorizationCodeFlowConfig, + }) + + return { credentialOffer, issuanceSession } + } + + public async getIssuanceSessionsById(agentReq: Req, sessionId: string) { + return agentReq.agent.modules.openid4vc.issuer?.getIssuanceSessionById(sessionId) + } + + public async getIssuanceSessionsByQuery( + agentReq: Req, + cNonce?: string, + publicIssuerId?: string, + preAuthorizedCode?: string, + state?: OpenId4VcIssuanceSessionState, + credentialOfferUri?: string, + authorizationCode?: string, + ) { + const issuanceSessionRepository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + const issuanceSessions = await issuanceSessionRepository.findByQuery(agentReq.agent.context, { + cNonce, + issuerId: publicIssuerId, + preAuthorizedCode, + state, + credentialOfferUri, + authorizationCode, + }) + + return issuanceSessions + } + + /** + * update an existing issuance session metadata, useful for mobile edge + * agents that will scan QR codes to notify the system of their + * wallet user id + * + * @param issuerAgent + * @param sessionId + * @param metadata + * @returns the updated issuance session record + */ + public async updateSessionIssuanceMetadataById(agentReq: Req, sessionId: string, metadata: Record) { + const issuanceSessionRepository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + + const record = await issuanceSessionRepository.findById(agentReq.agent.context, sessionId) + + if (!record) { + throw new NotFoundError(`Issuance session with id ${sessionId} not found`) + } + + record.issuanceMetadata = { + ...record.issuanceMetadata, + ...metadata, + } + + await issuanceSessionRepository.update(agentReq.agent.context, record) + + return record + } + + /** + * deletes ann issuance session by id + * + * @param sessionId + * @param issuerAgent + */ + public async deleteById(agentReq: Req, sessionId: string): Promise { + const issuanceSessionRepository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + await issuanceSessionRepository.deleteById(agentReq.agent.context, sessionId) + } +} + +export const issuanceSessionService = new IssuanceSessionsService() diff --git a/src/controllers/openid4vc/issuers/issuer.Controller.ts b/src/controllers/openid4vc/issuers/issuer.Controller.ts index 149ed0b8..8e6bfea4 100644 --- a/src/controllers/openid4vc/issuers/issuer.Controller.ts +++ b/src/controllers/openid4vc/issuers/issuer.Controller.ts @@ -1,108 +1,93 @@ -// import { -// Controller, -// Delete, -// Get, -// Post, -// Put, -// Route, -// Tags, -// Path, -// Query, -// Body, -// Security, -// Request, -// Example -// } from 'tsoa' -// import { injectable } from 'tsyringe' +import { Request as Req } from 'express' +import { Controller, Delete, Get, Post, Put, Route, Tags, Path, Query, Body, Security, Request, Example } from 'tsoa' +import { injectable } from 'tsyringe' -// import ErrorHandlingService from '../../../errorHandlingService' -// import { CreateIssuerOptions, UpdateIssuerRecordOptions } from '../types/issuer.types' -// import { Request as Req } from 'express' +import { SCOPES } from '../../../enums' +import ErrorHandlingService from '../../../errorHandlingService' +import { OpenId4VcUpdateIssuerRecordOptionsExample } from '../examples/issuer.examples' +import { CreateIssuerOptions, UpdateIssuerRecordOptions } from '../types/issuer.types' -// import { issuerService } from './issuer.service' -// import { SCOPES } from '../../../enums' -// import { OpenId4VcUpdateIssuerRecordOptionsExample } from '../examples/issuer.examples' -// @Route('/openid4vc/issuer') -// @Tags('oid4vc issuers') -// @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) -// @injectable() -// export class IssuerController extends Controller { -// /** -// * Creates an issuer with issuer metadata. -// */ -// @Post() -// @Example( -// OpenId4VcUpdateIssuerRecordOptionsExample.withScope.value -// ) -// public async createIssuer(@Request() request: Req, @Body() createIssuerOptions: CreateIssuerOptions) { -// try { -// return await issuerService.createIssuerAgent(request, createIssuerOptions) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } +import { issuerService } from './issuer.service' -// /** -// * Updates issuer metadata for a given publicIssuerId. -// */ -// @Put('{publicIssuerId}') -// public async updateIssuerMetadata( -// @Request() request: Req, -// @Path() publicIssuerId: string, -// @Body() updateIssuerRecordOptions: UpdateIssuerRecordOptions, -// ) { -// try { -// return await issuerService.updateIssuerMetadata(request, publicIssuerId, updateIssuerRecordOptions) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } +@Route('/openid4vc/issuer') +@Tags('oid4vc issuers') +@Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) +@injectable() +export class IssuerController extends Controller { + /** + * Creates an issuer with issuer metadata. + */ + @Post() + @Example(OpenId4VcUpdateIssuerRecordOptionsExample.withScope.value) + public async createIssuer(@Request() request: Req, @Body() createIssuerOptions: CreateIssuerOptions) { + try { + return await issuerService.createIssuerAgent(request, createIssuerOptions) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Returns metadata for a specific issuer. -// */ -// @Get('{issuerId}/metadata') -// public async getIssuerAgentMetaData(@Request() request: Req, @Path() issuerId: string) { -// try { -// return await issuerService.getIssuerAgentMetaData(request, issuerId) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } + /** + * Updates issuer metadata for a given publicIssuerId. + */ + @Put('{publicIssuerId}') + public async updateIssuerMetadata( + @Request() request: Req, + @Path() publicIssuerId: string, + @Body() updateIssuerRecordOptions: UpdateIssuerRecordOptions, + ) { + try { + return await issuerService.updateIssuerMetadata(request, publicIssuerId, updateIssuerRecordOptions) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Query issuers by optional publicIssuerId. -// */ -// @Get() -// public async getIssuersByQuery(@Request() request: Req, @Query() publicIssuerId?: string) { -// try { -// return await issuerService.getIssuersByQuery(request, publicIssuerId) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } + /** + * Returns metadata for a specific issuer. + */ + @Get('{issuerId}/metadata') + public async getIssuerAgentMetaData(@Request() request: Req, @Path() issuerId: string) { + try { + return await issuerService.getIssuerAgentMetaData(request, issuerId) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Returns a specific issuer by publicIssuerId. -// */ -// @Get('{publicIssuerId}') -// public async getIssuer(@Request() request: Req, @Path() publicIssuerId: string) { -// try { -// return await issuerService.getIssuer(request, publicIssuerId) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } + /** + * Query issuers by optional publicIssuerId. + */ + @Get() + public async getIssuersByQuery(@Request() request: Req, @Query() publicIssuerId?: string) { + try { + return await issuerService.getIssuersByQuery(request, publicIssuerId) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Deletes a specific issuer by record id. -// */ -// @Delete('{id}') -// public async deleteIssuer(@Request() request: Req, @Path() id: string): Promise { -// try { -// await issuerService.deleteIssuer(request, id) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } -// } + /** + * Returns a specific issuer by publicIssuerId. + */ + @Get('{publicIssuerId}') + public async getIssuer(@Request() request: Req, @Path() publicIssuerId: string) { + try { + return await issuerService.getIssuer(request, publicIssuerId) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } + + /** + * Deletes a specific issuer by record id. + */ + // @Delete('{id}') + // public async deleteIssuer(@Request() request: Req, @Path() id: string): Promise { + // try { + // await issuerService.deleteIssuer(request, id) + // } catch (error) { + // throw ErrorHandlingService.handle(error) + // } + // } +} diff --git a/src/controllers/openid4vc/issuers/issuer.service.ts b/src/controllers/openid4vc/issuers/issuer.service.ts index f1552097..b1f093de 100644 --- a/src/controllers/openid4vc/issuers/issuer.service.ts +++ b/src/controllers/openid4vc/issuers/issuer.service.ts @@ -1,57 +1,56 @@ -// import type { RestAgentModules, RestMultiTenantAgentModules } from '../../../cliAgent' -// import type { Agent } from '@credo-ts/core' -// // import { OpenId4VcIssuerRepository } from '@credo-ts/openid4vc/build/openid4vc-issuer/repository/OpenId4VcIssuerRepository.mjs' -// import { Request as Req } from 'express' - - -// export class IssuerService { -// public async createIssuerAgent( -// agentReq: Req, -// createIssuerOptions: any, //TODO: Replace with OpenId4VciCreateIssuerOptions, -// ) { -// const issuerRecord = await agentReq.agent.modules.openId4VcIssuer.createIssuer(createIssuerOptions) -// const issuerMetadata = await agentReq.agent.modules.openId4VcIssuer.getIssuerMetadata(issuerRecord.issuerId) -// // eslint-disable-next-line no-console -// console.log(`\nIssuer URL: ${issuerMetadata.credentialIssuer.credential_issuer}`) -// return issuerRecord -// } - -// public async updateIssuerMetadata( -// agentReq: Req, -// publicIssuerId: string, -// updateIssuerRecordOptions: any, // TODO: Replace with OpenId4VcUpdateIssuerRecordOptions -// ) { -// await agentReq.agent.modules.openId4VcIssuer.updateIssuerMetadata({ -// issuerId: publicIssuerId, -// ...updateIssuerRecordOptions, -// }) -// return await this.getIssuer(agentReq, publicIssuerId) -// } - -// public async getIssuersByQuery( -// agentReq: Req, -// publicIssuerId?: string, -// ) { -// const repository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuerRepository) -// return await repository.findByQuery(agentReq.agent.context, { issuerId: publicIssuerId }) -// } - -// public async getIssuer(agentReq:Req , publicIssuerId: string) { -// return await agentReq.agent.modules.openId4VcIssuer.getIssuerByIssuerId(publicIssuerId) -// } - -// public async deleteIssuer(agentReq: Req, issuerId: string) { -// const repository = agentReq.agent.dependencyManager.resolve(OpenId4VcIssuerRepository) -// await repository.deleteById(agentReq.agent.context, issuerId) -// } - -// public async getIssuerAgentMetaData( -// agentReq: Req, -// issuerId: string, -// ) { -// // return await agent.modules.openId4VcIssuer.getIssuerMetadata(issuerId) -// return 0 -// } -// } - -// export const issuerService = new IssuerService() +import type { RestAgentModules } from '../../../cliAgent' +import type { CreateIssuerOptions } from '../types/issuer.types' +import type { Agent } from '@credo-ts/core' +import type { Request as Req } from 'express' + +// import { OpenId4VcIssuerRepository } from '@credo-ts/openid4vc/build/openid4vc-issuer/repository/OpenId4VcIssuerRepository.mjs' + +export class IssuerService { + public async createIssuerAgent( + agentReq: Req, + createIssuerOptions: any, //TODO: Replace with OpenId4VciCreateIssuerOptions, + ) { + const issuerRecord = await agentReq.agent.modules.openid4vc.issuer?.createIssuer(createIssuerOptions) + const issuerMetadata = await agentReq.agent.modules.openid4vc.issuer?.getIssuerMetadata( + issuerRecord?.issuerId ?? '', + ) + // eslint-disable-next-line no-console + console.log(`\nIssuer URL: ${issuerMetadata?.credentialIssuer.credential_issuer}`) + return issuerRecord + } + + public async updateIssuerMetadata( + agentReq: Req, + publicIssuerId: string, + updateIssuerRecordOptions: any, // TODO: Replace with OpenId4VcUpdateIssuerRecordOptions + ) { + await agentReq.agent.modules.openid4vc.issuer?.updateIssuerMetadata({ + issuerId: publicIssuerId, + ...updateIssuerRecordOptions, + }) + return await this.getIssuer(agentReq, publicIssuerId) + } + + public async getIssuersByQuery(agentReq: Req, publicIssuerId?: string) { + const result = publicIssuerId + ? (agentReq.agent as Agent).openid4vc.issuer?.getIssuerByIssuerId(publicIssuerId) // .dependencyManager.resolve(OpenId4VcIssuerRepository) + : (agentReq.agent as Agent).openid4vc.issuer?.getAllIssuers() + return result + } + + public async getIssuer(agentReq: Req, publicIssuerId: string) { + return await agentReq.agent.modules.openid4vc.issuer?.getIssuerByIssuerId(publicIssuerId) + } + + // public async deleteIssuer(agentReq: Req, issuerId: string) { + // const result = (agentReq.agent as Agent).openid4vc.config.issuer. + // return result + // } + + public async getIssuerAgentMetaData(agentReq: Req, issuerId: string) { + return (await agentReq.agent.modules.openid4vc.issuer?.getIssuerMetadata(issuerId)) as any + // return 0 + } +} + +export const issuerService = new IssuerService() diff --git a/src/controllers/openid4vc/types/issuer.types.ts b/src/controllers/openid4vc/types/issuer.types.ts index 30f432d5..9d11017a 100644 --- a/src/controllers/openid4vc/types/issuer.types.ts +++ b/src/controllers/openid4vc/types/issuer.types.ts @@ -1,5 +1,8 @@ -import { MdocNameSpaces, W3cCredential } from "@credo-ts/core" -import { OpenId4VciCreateCredentialOfferOptions, OpenId4VciCredentialFormatProfile, OpenId4VciSignCredentials } from "@credo-ts/openid4vc" +import type { MdocNameSpaces, W3cCredential } from '@credo-ts/core' +import type { OpenId4VciCredentialFormatProfile } from '@credo-ts/openid4vc' + +import { Kms } from '@credo-ts/core' +import { OpenId4VciCreateCredentialOfferOptions, OpenId4VciSignCredentials } from '@credo-ts/openid4vc' export enum SignerMethod { Did = 'did', @@ -7,17 +10,17 @@ export enum SignerMethod { } export interface OpenId4VciOfferCredentials { - credentialSupportedId: string - format: OpenId4VciCredentialFormatProfile, + credentialSupportedId: string + format: OpenId4VciCredentialFormatProfile signerOptions: { method: SignerMethod did?: string x5c?: string[] + keyId: string } } export interface OpenId4VciOfferSdJwtCredential extends OpenId4VciOfferCredentials { - payload: { vct?: string [key: string]: unknown @@ -25,31 +28,31 @@ export interface OpenId4VciOfferSdJwtCredential extends OpenId4VciOfferCredentia disclosureFrame?: Record> } export interface ValidityInfo { - signed: Date; - validFrom: Date; - validUntil: Date; - expectedUpdate?: Date; + signed: Date + validFrom: Date + validUntil: Date + expectedUpdate?: Date } -export interface OpenId4VciOfferMdocCredential extends OpenId4VciOfferCredentials { +export interface OpenId4VciOfferMdocCredential extends OpenId4VciOfferCredentials { payload: { docType: 'org.iso.18013.5.1.mDL' | (string & {}) - validityInfo?: Partial, - namespaces: MdocNameSpaces + validityInfo?: Partial + namespaces: MdocNameSpaces } } -export interface OpenId4VciOfferW3cCredential extends OpenId4VciOfferCredentials { +export interface OpenId4VciOfferW3cCredential extends OpenId4VciOfferCredentials { payload: { - verificationMethod: string; - credential: W3cCredential; + verificationMethod: string + credential: W3cCredential } } - -export interface OpenId4VcIssuanceSessionsCreateOffer {//extends OpenId4VciCreateCredentialOfferOptions { +export interface OpenId4VcIssuanceSessionsCreateOffer { + //extends OpenId4VciCreateCredentialOfferOptions { publicIssuerId: string - credentials: Array + credentials: Array authorizationCodeFlowConfig?: { authorizationServerUrl: string requirePresentationDuringIssuance?: boolean @@ -104,8 +107,13 @@ export interface BatchCredentialIssuanceOptions { batchSize: number } +export interface KeyAttestationRequiredRecords { + key_storage: string[] + user_authentication: string[] +} export interface ProofTypeConfig { proof_signing_alg_values_supported: string[] + key_attestations_required?: KeyAttestationRequiredRecords } export interface CredentialConfigurationDisplay { @@ -123,21 +131,35 @@ export interface CredentialDefinition { [key: string]: any } +export interface Claim { + path: string[] + display?: ClaimDisplay[] + mandatory?: boolean +} + +export interface ClaimDisplay { + name: string + locale: string +} +export interface CredentialMetadata { + display: CredentialDisplay[] + claims: Claim[] +} + export interface CredentialConfigurationSupportedWithFormats { - format: 'vc+sd-jwt' | 'mso_mdoc' | 'jwt_vc_json' | string, - vct?: string, - doctype?: string, + format: 'vc+sd-jwt' | 'mso_mdoc' | 'jwt_vc_json' | string + vct?: string + doctype?: string scope?: string - claims?: any cryptographic_binding_methods_supported?: string[] - credential_signing_alg_values_supported?: string[] + credential_signing_alg_values_supported?: string[] | number[] proof_types_supported?: Record credential_definition?: CredentialDefinition - display?: CredentialConfigurationDisplay[] + credential_metadata?: CredentialMetadata } export interface CreateIssuerOptions { issuerId?: string - accessTokenSignerKeyType?: string + accessTokenSignerKeyType?: any display?: CredentialDisplay[] authorizationServerConfigs?: AuthorizationServerConfig[] dpopSigningAlgValuesSupported?: string[] diff --git a/src/controllers/openid4vc/verifier-sessions/verification-sessions.Controller.ts b/src/controllers/openid4vc/verifier-sessions/verification-sessions.Controller.ts index 83dd01c6..603cd542 100644 --- a/src/controllers/openid4vc/verifier-sessions/verification-sessions.Controller.ts +++ b/src/controllers/openid4vc/verifier-sessions/verification-sessions.Controller.ts @@ -1,87 +1,88 @@ -// import { Agent } from '@credo-ts/core' -// import { OpenId4VcVerificationSessionState } from '@credo-ts/openid4vc' -// import { Controller, Get, Path, Query, Route, Request, Security, Tags, Post, Body } from 'tsoa' -// import { injectable } from 'tsyringe' -// import ErrorHandlingService from '../../../errorHandlingService' +import { Agent } from '@credo-ts/core' +import { OpenId4VcVerificationSessionState } from '@credo-ts/openid4vc' +import { Request as Req } from 'express' +import { Controller, Get, Path, Query, Route, Request, Security, Tags, Post, Body } from 'tsoa' +import { injectable } from 'tsyringe' -// import { verificationSessionService } from './verification-sessions.service' -// import { SCOPES } from '../../../enums' -// import { Request as Req } from 'express' -// import { CreateAuthorizationRequest } from '../types/verifier.types' +import { SCOPES } from '../../../enums' +import ErrorHandlingService from '../../../errorHandlingService' +import { CreateAuthorizationRequest } from '../types/verifier.types' -// @Tags('oid4vc verification sessions') -// @Route('/openid4vc/verification-sessions') -// @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) -// @injectable() -// export class VerificationSessionsController extends Controller { -// /** -// * Create an authorization request, acting as a Relying Party (RP) -// */ -// @Post('/create-presentation-request') -// public async createProofRequest( -// @Request() request: Req, -// @Body() createAuthorizationRequest: CreateAuthorizationRequest, -// ) { -// try { -// return await verificationSessionService.createProofRequest(request, createAuthorizationRequest) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } +import { verificationSessionService } from './verification-sessions.service' -// /** -// * Retrieve all verification session records -// */ -// @Get('/') -// public async getAllVerificationSessions( -// @Request() request: Req, -// @Query('publicVerifierId') publicVerifierId?: string, -// @Query('payloadState') payloadState?: string, -// @Query('state') state?: OpenId4VcVerificationSessionState, -// @Query('authorizationRequestUri') authorizationRequestUri?: string, -// @Query('nonce') nonce?: string, -// ) { -// try { -// return await verificationSessionService.findVerificationSessionsByQuery( -// request, -// publicVerifierId, -// payloadState, -// state, -// authorizationRequestUri, -// nonce, -// ) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } +@Tags('oid4vc verification sessions') +@Route('/openid4vc/verification-sessions') +@Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) +@injectable() +export class VerificationSessionsController extends Controller { + /** + * Create an authorization request, acting as a Relying Party (RP) + */ + @Post('/create-presentation-request') + public async createProofRequest( + @Request() request: Req, + @Body() createAuthorizationRequest: CreateAuthorizationRequest, + ) { + try { + return await verificationSessionService.createProofRequest(request, createAuthorizationRequest) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } -// /** -// * Get verification session by ID -// */ -// @Get('/:verificationSessionId') -// public async getVerificationSessionsById( -// @Request() request: Req, -// @Path('verificationSessionId') verificationSessionId: string, -// ) { -// try { -// return await verificationSessionService.getVerificationSessionsById(request, verificationSessionId) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } -// // TODO: Uncomment when the method is implemented: There was a problem resolving type of 'IDTokenPayload'. -// // /** -// // * Get verification response by verification Session ID -// // */ -// @Get('/response/:verificationSessionId') -// public async getVerifiedAuthorizationResponse( -// @Request() request: Req, -// @Path('verificationSessionId') verificationSessionId: string, -// ) { -// try { -// return await verificationSessionService.getVerifiedAuthorizationResponse(request, verificationSessionId) -// } catch (error) { -// throw ErrorHandlingService.handle(error) -// } -// } -// } + /** + * Retrieve all verification session records + */ + @Get('/') + public async getAllVerificationSessions( + @Request() request: Req, + @Query('publicVerifierId') publicVerifierId?: string, + @Query('payloadState') payloadState?: string, + @Query('state') state?: OpenId4VcVerificationSessionState, + @Query('authorizationRequestUri') authorizationRequestUri?: string, + @Query('nonce') nonce?: string, + ) { + try { + return await verificationSessionService.findVerificationSessionsByQuery( + request, + publicVerifierId, + payloadState, + state, + authorizationRequestUri, + nonce, + ) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } + + /** + * Get verification session by ID + */ + @Get('/:verificationSessionId') + public async getVerificationSessionsById( + @Request() request: Req, + @Path('verificationSessionId') verificationSessionId: string, + ) { + try { + return await verificationSessionService.getVerificationSessionsById(request, verificationSessionId) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } + // TODO: Uncomment when the method is implemented: There was a problem resolving type of 'IDTokenPayload'. + // /** + // * Get verification response by verification Session ID + // */ + @Get('/response/:verificationSessionId') + public async getVerifiedAuthorizationResponse( + @Request() request: Req, + @Path('verificationSessionId') verificationSessionId: string, + ) { + try { + return await verificationSessionService.getVerifiedAuthorizationResponse(request, verificationSessionId) + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } +} diff --git a/src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts b/src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts index 1a648246..b5cd628c 100644 --- a/src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts +++ b/src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts @@ -1,209 +1,206 @@ -// import type { RestAgentModules, RestMultiTenantAgentModules } from '../../../cliAgent' - -// import { -// Agent, -// ClaimFormat, -// DidKey, -// JsonEncoder, -// JsonTransformer, -// Jwt, -// Mdoc, -// MdocDeviceResponse, -// RecordNotFoundError, -// TypedArrayEncoder, -// W3cJsonLdVerifiablePresentation, -// W3cJwtVerifiablePresentation, -// W3cPresentation, -// X509Service, -// } from '@credo-ts/core' -// import { OpenId4VcJwtIssuerDid, OpenId4VcVerificationSessionState } from '@credo-ts/openid4vc' -// import { injectable } from 'tsyringe' -// import { Request as Req } from 'express' -// import { SignerMethod } from '../../../enums' -// import { CreateAuthorizationRequest, OpenId4VcIssuerX5c, ResponseModeEnum } from '../types/verifier.types' - -// // import { CreateAuthorizationRequest } from '../types/verifier.types' - -// @injectable() -// class VerificationSessionsService { -// public async createProofRequest(agentReq: Req, dto: CreateAuthorizationRequest) { -// try { -// let requestSigner -// if (dto.requestSigner.method === SignerMethod.Did) { -// requestSigner = dto.requestSigner as OpenId4VcJwtIssuerDid - -// const didToResolve = dto.requestSigner?.didUrl -// if (!didToResolve) { -// throw new Error('No DID provided to resolve (neither requestSigner.didUrl nor verifierDid present)') -// } - -// const didDocument = await agentReq.agent.dids.resolveDidDocument(didToResolve) - -// let verifierDidUrl: string | undefined = undefined -// if (didDocument.verificationMethod?.[0]?.id) { -// verifierDidUrl = didDocument.verificationMethod[0].id -// } - -// if (!verifierDidUrl) { -// throw new Error('No matching verification method found on verifier DID document') -// } - -// if (!requestSigner.didUrl || !String(requestSigner.didUrl).includes('#')) { -// requestSigner.didUrl = verifierDidUrl -// } - -// requestSigner = { method: 'did', didUrl: verifierDidUrl } as any -// } else { -// requestSigner = dto.requestSigner as OpenId4VcIssuerX5c - -// const parsedCertificate = X509Service.parseCertificate(agentReq.agent.context, { -// encodedCertificate: requestSigner.x5c[0], -// }) -// requestSigner.issuer = parsedCertificate.sanUriNames[0] -// } - -// if (!requestSigner) { -// } else if (requestSigner.method === 'did') { -// } -// const options: any = { -// requestSigner, -// verifierId: dto.verifierId, -// } - -// if(dto.responseMode === ResponseModeEnum.DC_API || ResponseModeEnum.DC_API_JWT){ -// options.expectedOrigins = dto.expectedOrigins -// } - -// if (dto.responseMode) options.responseMode = dto.responseMode -// if (dto.presentationExchange) { -// options.presentationExchange = dto.presentationExchange -// } else if (dto.dcql) { -// options.dcql = dto.dcql -// } - -// return (await agentReq.agent.modules.openId4VcVerifier.createAuthorizationRequest(options)) as any -// } catch (error) { -// throw error -// } -// } - -// public async findVerificationSessionsByQuery( -// agentReq: Req, -// publicVerifierId?: string, -// payloadState?: string, -// state?: OpenId4VcVerificationSessionState, -// authorizationRequestUri?: string, -// nonce?: string, -// ) { -// return await agentReq.agent.modules.openId4VcVerifier.findVerificationSessionsByQuery({ -// verifierId: publicVerifierId, -// payloadState, -// state, -// authorizationRequestUri, -// nonce, -// }) -// } - -// public async getVerificationSessionsById(agentReq: Req, verificationSessionId: string) { -// return await agentReq.agent.modules.openId4VcVerifier.getVerificationSessionById(verificationSessionId) -// } - -// public async getVerifiedAuthorizationResponse(request: Req, verificationSessionId: string) { -// const verificationSession = -// await request.agent.modules.openId4VcVerifier.getVerificationSessionById(verificationSessionId) -// const verified = await request.agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse( -// verificationSession.id, -// ) -// console.log(verified.presentationExchange?.presentations) -// console.log(verified.dcql?.presentationResult) - -// const presentations = await Promise.all( -// (verified.presentationExchange?.presentations ?? Object.values(verified.dcql?.presentations ?? {})) -// .flat() -// .map(async (presentation: { toJson: () => any; presentation: W3cPresentation; serializedJwt: any; documents: Mdoc[]; base64Url: any; compact: any }) => { -// if (presentation instanceof W3cJsonLdVerifiablePresentation) { -// return { -// pretty: presentation.toJson(), -// encoded: presentation.toJson(), -// } -// } - -// if (presentation instanceof W3cJwtVerifiablePresentation) { -// return { -// pretty: JsonTransformer.toJSON(presentation.presentation), -// encoded: presentation.serializedJwt, -// } -// } - -// if (presentation instanceof MdocDeviceResponse) { -// return { -// pretty: JsonTransformer.toJSON({ -// documents: presentation.documents.map((doc) => ({ -// doctype: doc.docType, -// alg: doc.alg, -// base64Url: doc.base64Url, -// validityInfo: doc.validityInfo, -// deviceSignedNamespaces: doc.deviceSignedNamespaces, -// issuerSignedNamespaces: Object.entries(doc.issuerSignedNamespaces).map( -// ([nameSpace, nameSpacEntries]) => [ -// nameSpace, -// Object.entries(nameSpacEntries).map(([key, value]) => -// value instanceof Uint8Array -// ? [`base64:${key}`, `data:image/jpeg;base64,${TypedArrayEncoder.toBase64(value)}`] -// : [key, value], -// ), -// ], -// ), -// })), -// }), -// encoded: presentation.base64Url, -// } -// } - -// // if ( -// // presentation instanceof W3cV2JwtVerifiablePresentation || -// // presentation instanceof W3cV2SdJwtVerifiablePresentation -// // ) { -// // throw new Error('W3C V2 presentations are not supported yet') -// // } - -// return { -// pretty: { -// ...presentation, -// compact: undefined, -// }, -// encoded: presentation.compact, -// } -// }) ?? [], -// ) - -// const dcqlSubmission = verified.dcql -// ? Object.keys(verified.dcql.presentations).map((key, index) => ({ -// queryCredentialId: key, -// presentationIndex: index, -// })) -// : undefined - -// console.log('presentations', presentations) - -// return { -// verificationSessionId: verificationSession.id, -// responseStatus: verificationSession.state, -// error: verificationSession.errorMessage, -// //authorizationRequest, - -// presentations: presentations, - -// submission: verified.presentationExchange?.submission, -// definition: verified.presentationExchange?.definition, -// transactionDataSubmission: verified.transactionData, - -// // dcqlQuery, -// dcqlSubmission: verified.dcql -// ? { ...verified.dcql.presentationResult, vpTokenMapping: dcqlSubmission } -// : undefined, -// } as any -// } -// } - -// export const verificationSessionService = new VerificationSessionsService() +import type { RestAgentModules, RestMultiTenantAgentModules } from '../../../cliAgent' + +import { + Agent, + ClaimFormat, + DidKey, + JsonEncoder, + JsonTransformer, + Jwt, + Mdoc, + MdocDeviceResponse, + RecordNotFoundError, + TypedArrayEncoder, + W3cJsonLdVerifiablePresentation, + W3cJwtVerifiablePresentation, + W3cPresentation, + X509Service, +} from '@credo-ts/core' +import { OpenId4VcJwtIssuerDid, OpenId4VcVerificationSessionState } from '@credo-ts/openid4vc' +import { Request as Req } from 'express' +import { injectable } from 'tsyringe' + +import { SignerMethod } from '../../../enums' +import { CreateAuthorizationRequest, OpenId4VcIssuerX5c, ResponseModeEnum } from '../types/verifier.types' + +// import { CreateAuthorizationRequest } from '../types/verifier.types' + +@injectable() +class VerificationSessionsService { + public async createProofRequest(agentReq: Req, dto: CreateAuthorizationRequest) { + try { + let requestSigner + if (dto.requestSigner.method === SignerMethod.Did) { + requestSigner = dto.requestSigner as OpenId4VcJwtIssuerDid + + const didToResolve = dto.requestSigner?.didUrl + if (!didToResolve) { + throw new Error('No DID provided to resolve (neither requestSigner.didUrl nor verifierDid present)') + } + + const didDocument = await agentReq.agent.dids.resolveDidDocument(didToResolve) + + let verifierDidUrl: string | undefined = undefined + if (didDocument.verificationMethod?.[0]?.id) { + verifierDidUrl = didDocument.verificationMethod[0].id + } + + if (!verifierDidUrl) { + throw new Error('No matching verification method found on verifier DID document') + } + + if (!requestSigner.didUrl || !String(requestSigner.didUrl).includes('#')) { + requestSigner.didUrl = verifierDidUrl + } + + requestSigner = { method: 'did', didUrl: verifierDidUrl } as any + } else { + requestSigner = dto.requestSigner as OpenId4VcIssuerX5c + + const parsedCertificate = X509Service.parseCertificate(agentReq.agent.context, { + encodedCertificate: requestSigner.x5c[0], + }) + requestSigner.issuer = parsedCertificate.sanUriNames[0] + } + + if (!requestSigner) { + } else if (requestSigner.method === 'did') { + } + const options: any = { + requestSigner, + verifierId: dto.verifierId, + } + + if (dto.responseMode === ResponseModeEnum.DC_API || ResponseModeEnum.DC_API_JWT) { + options.expectedOrigins = dto.expectedOrigins + } + + if (dto.responseMode) options.responseMode = dto.responseMode + if (dto.presentationExchange) { + options.presentationExchange = dto.presentationExchange + } else if (dto.dcql) { + options.dcql = dto.dcql + } + + return (await agentReq.agent.modules.openid4vc.verifier?.createAuthorizationRequest(options)) as any + } catch (error) { + throw error + } + } + + public async findVerificationSessionsByQuery( + agentReq: Req, + publicVerifierId?: string, + payloadState?: string, + state?: OpenId4VcVerificationSessionState, + authorizationRequestUri?: string, + nonce?: string, + ) { + return await agentReq.agent.modules.openid4vc.verifier?.findVerificationSessionsByQuery({ + verifierId: publicVerifierId, + payloadState, + state, + authorizationRequestUri, + nonce, + }) + } + + public async getVerificationSessionsById(agentReq: Req, verificationSessionId: string) { + return await agentReq.agent.modules.openid4vc.verifier?.getVerificationSessionById(verificationSessionId) + } + + public async getVerifiedAuthorizationResponse(request: Req, verificationSessionId: string) { + const verificationSession = + await request.agent.modules.openid4vc.verifier?.getVerificationSessionById(verificationSessionId) + const verified = await request.agent.modules.openid4vc.verifier?.getVerifiedAuthorizationResponse( + verificationSession!.id, + ) + + const presentations = await Promise.all( + (verified!.presentationExchange?.presentations ?? Object.values(verified!.dcql?.presentations ?? {})) + .flat() + .map(async (presentation: any) => { + if (presentation instanceof W3cJsonLdVerifiablePresentation) { + return { + pretty: presentation.toJson(), + encoded: presentation.toJson(), + } + } + + if (presentation instanceof W3cJwtVerifiablePresentation) { + return { + pretty: JsonTransformer.toJSON(presentation.presentation), + encoded: presentation.serializedJwt, + } + } + + if (presentation instanceof MdocDeviceResponse) { + return { + pretty: JsonTransformer.toJSON({ + documents: presentation.documents.map((doc) => ({ + doctype: doc.docType, + alg: doc.alg, + base64Url: doc.base64Url, + validityInfo: doc.validityInfo, + deviceSignedNamespaces: doc.deviceSignedNamespaces, + issuerSignedNamespaces: Object.entries(doc.issuerSignedNamespaces).map( + ([nameSpace, nameSpacEntries]) => [ + nameSpace, + Object.entries(nameSpacEntries).map(([key, value]) => + value instanceof Uint8Array + ? [`base64:${key}`, `data:image/jpeg;base64,${TypedArrayEncoder.toBase64(value)}`] + : [key, value], + ), + ], + ), + })), + }), + encoded: presentation.base64Url, + } + } + + // if ( + // presentation instanceof W3cV2JwtVerifiablePresentation || + // presentation instanceof W3cV2SdJwtVerifiablePresentation + // ) { + // throw new Error('W3C V2 presentations are not supported yet') + // } + + return { + pretty: { + ...presentation, + compact: undefined, + }, + encoded: presentation.compact, + } + }) ?? [], + ) + + const dcqlSubmission = verified!.dcql + ? Object.keys(verified!.dcql.presentations).map((key, index) => ({ + queryCredentialId: key, + presentationIndex: index, + })) + : undefined + + return { + verificationSessionId: verificationSession?.id, + responseStatus: verificationSession?.state, + error: verificationSession?.errorMessage, + //authorizationRequest, + + presentations: presentations, + + submission: verified!.presentationExchange?.submission, + definition: verified!.presentationExchange?.definition, + transactionDataSubmission: verified!.transactionData, + + // dcqlQuery, + dcqlSubmission: verified!.dcql + ? { ...verified!.dcql.presentationResult, vpTokenMapping: dcqlSubmission } + : undefined, + } as any + } +} + +export const verificationSessionService = new VerificationSessionsService() diff --git a/src/controllers/x509/x509.service.ts b/src/controllers/x509/x509.service.ts index 8e0ca051..efad9ae8 100644 --- a/src/controllers/x509/x509.service.ts +++ b/src/controllers/x509/x509.service.ts @@ -1,11 +1,10 @@ - import type { BasicX509CreateCertificateConfig, X509ImportCertificateOptionsDto } from '../types' import type { CredoError } from '@credo-ts/core' import type { Request as Req } from 'express' import { transformPrivateKeyToPrivateJwk, transformSeedToPrivateJwk } from '@credo-ts/askar' import { - Kms, + Kms, TypedArrayEncoder, X509Certificate, X509ExtendedKeyUsage, @@ -15,13 +14,13 @@ import { type Agent, } from '@credo-ts/core' import { KeyAlgorithm } from '@openwallet-foundation/askar-nodejs' +import { error } from 'console' import { keyAlgorithmToCurve } from '../../utils/constant' import { generateSecretKey, getCertificateValidityForSystem, getTypeFromCurve } from '../../utils/helpers' import { pemToRawEd25519PrivateKey } from './crypto-util' import { type X509CreateCertificateOptionsDto } from './x509.types' -import { error } from 'console' class x509Service { public async createSelfSignedDCS(createX509Options: BasicX509CreateCertificateConfig, agentReq: Req) { @@ -79,24 +78,24 @@ class x509Service { public async createCertificate(agentReq: Req, options: X509CreateCertificateOptionsDto) { const agent = agentReq.agent - let authorityKeyID, subjectPublicKeyID + let authorityKeyID, subjectPublicKeyID, authorityKeyKmsId - agent.config.logger.debug(`createCertificate options:`, options) + agent.config.logger.debug(`createCertificate options:`, options) - if (options.authorityKey && options?.authorityKey?.seed) { - const { privateJwk } = transformSeedToPrivateJwk({ - type: getTypeFromCurve(options.authorityKey.keyType ?? 'P-256'), - seed: TypedArrayEncoder.fromString(options.authorityKey!.seed!), - }) - + const { privateJwk } = transformSeedToPrivateJwk({ + type: getTypeFromCurve(options.authorityKey.keyType ?? 'P-256'), + seed: TypedArrayEncoder.fromString(options.authorityKey!.seed!), + }) + const { publicJwk } = await agent.kms.importKey({ privateJwk }) authorityKeyID = publicJwk } else { - const { publicJwk } = await agent.kms.createKey({ - type: getTypeFromCurve(options.authorityKey?.keyType ?? 'P-256') - }) - authorityKeyID = publicJwk + const { publicJwk, keyId } = await agent.kms.createKey({ + type: getTypeFromCurve(options.authorityKey?.keyType ?? 'P-256'), + }) + authorityKeyID = publicJwk + authorityKeyKmsId = keyId } if (options.subjectPublicKey) { @@ -109,19 +108,17 @@ class x509Service { }) subjectPublicKeyID = importedKey.publicJwk - } else { const { keyId, publicJwk } = await agent.kms.createKey({ - type: getTypeFromCurve(options.subjectPublicKey?.keyType ?? 'P-256') + type: getTypeFromCurve(options.subjectPublicKey?.keyType ?? 'P-256'), }) subjectPublicKeyID = publicJwk } } - agent.config.logger.info('This is subjectPublicKeyID', subjectPublicKeyID) const certificate = await agent.x509.createCertificate({ - // authorityKey: authorityKeyID as Key, + // authorityKey: authorityKeyID as Key, authorityKey: Kms.PublicJwk.fromPublicJwk(authorityKeyID), // subjectPublicKey: Kms.PublicJwk.fromPublicJwk(subjectPublicKeyID!), serialNumber: options.serialNumber, @@ -130,10 +127,10 @@ class x509Service { subject: options.subject, validity: options.validity, }) - agent.config.logger.info("Result") const issuerCertificate = certificate.toString('base64') - return { publicCertificateBase64: issuerCertificate } + + return { publicCertificateBase64: issuerCertificate, keyId: authorityKeyKmsId } } public async ImportX509Certificates(agentReq: Req, options: X509ImportCertificateOptionsDto) { @@ -169,8 +166,9 @@ class x509Service { agent.config.logger.error(`Error caught`) // If the key already exists, we assume the self-signed certificate is already created - if (error instanceof Kms.KeyManagementKeyExistsError) { - console.error( 'key already exists while importing certificate') + if (error instanceof Kms.KeyManagementKeyExistsError) { + // eslint-disable-next-line no-console + console.error('key already exists while importing certificate') } else { agent.config.logger.error(`${JSON.stringify(error)}`) throw error diff --git a/src/routes/swagger.json b/src/routes/swagger.json index 41ebc7e3..7f6d3fe4 100644 --- a/src/routes/swagger.json +++ b/src/routes/swagger.json @@ -416,11 +416,6 @@ ], "type": "string" }, - "Record_string.unknown_": { - "properties": {}, - "type": "object", - "description": "Construct a type with a set of properties K of type T" - }, "RecordId": { "type": "string", "example": "821f9b26-ad04-4f56-89b6-e2ef9c72b36e" @@ -521,40 +516,6 @@ "type": "object", "additionalProperties": false }, - "JsonValue": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number", - "format": "double" - }, - { - "type": "boolean" - }, - { - "$ref": "#/components/schemas/JsonObject" - }, - { - "$ref": "#/components/schemas/JsonArray" - } - ], - "nullable": true - }, - "JsonObject": { - "properties": {}, - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/JsonValue" - } - }, - "JsonArray": { - "items": { - "$ref": "#/components/schemas/JsonValue" - }, - "type": "array" - }, "Pick_JwsGeneralFormat.Exclude_keyofJwsGeneralFormat.payload__": { "properties": { "header": { @@ -1419,88 +1380,6 @@ } ] }, - "W3cIssuer": { - "properties": { - "id": { - "type": "string" - } - }, - "required": [ - "id" - ], - "type": "object", - "additionalProperties": false - }, - "W3cCredentialSubject": { - "properties": { - "id": { - "type": "string" - }, - "claims": { - "$ref": "#/components/schemas/Record_string.unknown_" - } - }, - "type": "object", - "additionalProperties": false - }, - "SingleOrArray_W3cCredentialSubject_": { - "anyOf": [ - { - "$ref": "#/components/schemas/W3cCredentialSubject" - }, - { - "items": { - "$ref": "#/components/schemas/W3cCredentialSubject" - }, - "type": "array" - } - ] - }, - "W3cCredentialSchema": { - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "SingleOrArray_W3cCredentialSchema_": { - "anyOf": [ - { - "$ref": "#/components/schemas/W3cCredentialSchema" - }, - { - "items": { - "$ref": "#/components/schemas/W3cCredentialSchema" - }, - "type": "array" - } - ] - }, - "W3cCredentialStatus": { - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object", - "additionalProperties": false - }, "W3cJsonLdVerifiableCredential": { "properties": { "context": { @@ -1583,76 +1462,893 @@ } }, "required": [ - "credential", - "proof" + "credential", + "proof" + ], + "type": "object", + "additionalProperties": false + }, + "TenantConfig": { + "properties": { + "label": { + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object" + }, + "MetadataValue": { + "$ref": "#/components/schemas/Record_string.any_" + }, + "MetadataBase": { + "properties": {}, + "additionalProperties": { + "$ref": "#/components/schemas/MetadataValue" + }, + "type": "object" + }, + "Metadata____": { + "description": "Metadata access class to get, set (create and update), add (append to a record) and delete metadata on any record.\n\nset will override the previous value if it already exists\n\nnote: To add persistence to these records, you have to update the record in the correct repository", + "properties": { + "data": { + "$ref": "#/components/schemas/MetadataBase" + } + }, + "required": [ + "data" + ], + "type": "object", + "additionalProperties": false + }, + "Pick_CustomTenantConfig.Exclude_keyofCustomTenantConfig.walletConfig__": { + "properties": { + "label": { + "type": "string" + }, + "connectionImageUrl": { + "type": "string" + } + }, + "required": [ + "label" + ], + "type": "object", + "description": "From T, pick a set of properties whose keys are in the union K" + }, + "Omit_CustomTenantConfig.walletConfig_": { + "$ref": "#/components/schemas/Pick_CustomTenantConfig.Exclude_keyofCustomTenantConfig.walletConfig__", + "description": "Construct a type with the properties of T except for those in type K." + }, + "CreateTenantOptions": { + "properties": { + "config": { + "$ref": "#/components/schemas/Omit_CustomTenantConfig.walletConfig_" + } + }, + "required": [ + "config" + ], + "type": "object", + "additionalProperties": false + }, + "JwtObject": { + "properties": { + "alg": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "alg" + ], + "type": "object", + "additionalProperties": false + }, + "LdpObject": { + "properties": { + "proof_type": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "proof_type" + ], + "type": "object", + "additionalProperties": false + }, + "DiObject": { + "properties": { + "proof_type": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cryptosuite": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "proof_type", + "cryptosuite" + ], + "type": "object", + "additionalProperties": false + }, + "SdJwtObject": { + "properties": { + "undefined": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "MsoMdocObject": { + "properties": { + "alg": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "alg" + ], + "type": "object", + "additionalProperties": false + }, + "Format": { + "properties": { + "jwt": { + "$ref": "#/components/schemas/JwtObject" + }, + "jwt_vc": { + "$ref": "#/components/schemas/JwtObject" + }, + "jwt_vc_json": { + "$ref": "#/components/schemas/JwtObject" + }, + "jwt_vp": { + "$ref": "#/components/schemas/JwtObject" + }, + "jwt_vp_json": { + "$ref": "#/components/schemas/JwtObject" + }, + "ldp": { + "$ref": "#/components/schemas/LdpObject" + }, + "ldp_vc": { + "$ref": "#/components/schemas/LdpObject" + }, + "ldp_vp": { + "$ref": "#/components/schemas/LdpObject" + }, + "di": { + "$ref": "#/components/schemas/DiObject" + }, + "di_vc": { + "$ref": "#/components/schemas/DiObject" + }, + "di_vp": { + "$ref": "#/components/schemas/DiObject" + }, + "undefined": { + "$ref": "#/components/schemas/SdJwtObject" + }, + "mso_mdoc": { + "$ref": "#/components/schemas/MsoMdocObject" + } + }, + "type": "object", + "additionalProperties": false + }, + "Issuance": { + "properties": { + "manifest": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": {} + }, + "Optionality": { + "type": "string", + "enum": [ + "required", + "preferred" + ] + }, + "Directives": { + "type": "string", + "enum": [ + "required", + "allowed", + "disallowed" + ] + }, + "PdStatus": { + "properties": { + "directive": { + "$ref": "#/components/schemas/Directives" + } + }, + "type": "object", + "additionalProperties": false + }, + "Statuses": { + "properties": { + "active": { + "$ref": "#/components/schemas/PdStatus" + }, + "suspended": { + "$ref": "#/components/schemas/PdStatus" + }, + "revoked": { + "$ref": "#/components/schemas/PdStatus" + } + }, + "type": "object", + "additionalProperties": false + }, + "OneOfNumberStringBoolean": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "number", + "format": "double" + }, + { + "type": "string" + } + ] + }, + "OneOfNumberString": { + "anyOf": [ + { + "type": "number", + "format": "double" + }, + { + "type": "string" + } + ] + }, + "FilterV2": { + "properties": { + "const": { + "$ref": "#/components/schemas/OneOfNumberStringBoolean" + }, + "enum": { + "items": { + "$ref": "#/components/schemas/OneOfNumberStringBoolean" + }, + "type": "array" + }, + "exclusiveMinimum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number", + "format": "double" + }, + "maxLength": { + "type": "number", + "format": "double" + }, + "minimum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "maximum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "not": { + "additionalProperties": false, + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/components/schemas/FilterV2" + }, + "items": { + "$ref": "#/components/schemas/FilterV2Items" + } + }, + "type": "object", + "additionalProperties": false + }, + "FilterV2Items": { + "properties": { + "const": { + "$ref": "#/components/schemas/OneOfNumberStringBoolean" + }, + "enum": { + "items": { + "$ref": "#/components/schemas/OneOfNumberStringBoolean" + }, + "type": "array" + }, + "exclusiveMinimum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "exclusiveMaximum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "format": { + "type": "string" + }, + "formatMaximum": { + "type": "string" + }, + "formatMinimum": { + "type": "string" + }, + "formatExclusiveMaximum": { + "type": "string" + }, + "formatExclusiveMinimum": { + "type": "string" + }, + "minLength": { + "type": "number", + "format": "double" + }, + "maxLength": { + "type": "number", + "format": "double" + }, + "minimum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "maximum": { + "$ref": "#/components/schemas/OneOfNumberString" + }, + "not": { + "additionalProperties": false, + "type": "object" + }, + "pattern": { + "type": "string" + }, + "type": { + "type": "string" + }, + "contains": { + "$ref": "#/components/schemas/FilterV2" + }, + "items": { + "$ref": "#/components/schemas/FilterV2Items" + } + }, + "type": "object", + "additionalProperties": false + }, + "FieldV2": { + "properties": { + "id": { + "type": "string" + }, + "path": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purpose": { + "type": "string" + }, + "filter": { + "$ref": "#/components/schemas/FilterV2" + }, + "predicate": { + "$ref": "#/components/schemas/Optionality" + }, + "intent_to_retain": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "path" + ], + "type": "object", + "additionalProperties": false + }, + "HolderSubject": { + "properties": { + "field_id": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directive": { + "$ref": "#/components/schemas/Optionality" + } + }, + "required": [ + "field_id", + "directive" + ], + "type": "object", + "additionalProperties": false + }, + "ConstraintsV2": { + "properties": { + "limit_disclosure": { + "$ref": "#/components/schemas/Optionality" + }, + "statuses": { + "$ref": "#/components/schemas/Statuses" + }, + "fields": { + "items": { + "$ref": "#/components/schemas/FieldV2" + }, + "type": "array" + }, + "subject_is_issuer": { + "$ref": "#/components/schemas/Optionality" + }, + "is_holder": { + "items": { + "$ref": "#/components/schemas/HolderSubject" + }, + "type": "array" + }, + "same_subject": { + "items": { + "$ref": "#/components/schemas/HolderSubject" + }, + "type": "array" + } + }, + "type": "object", + "additionalProperties": false + }, + "InputDescriptorV2Model": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/components/schemas/Format" + }, + "group": { + "items": { + "type": "string" + }, + "type": "array" + }, + "issuance": { + "items": { + "$ref": "#/components/schemas/Issuance" + }, + "type": "array" + }, + "constraints": { + "$ref": "#/components/schemas/ConstraintsV2" + } + }, + "required": [ + "id", + "constraints" + ], + "type": "object", + "additionalProperties": false + }, + "Rules": { + "type": "string", + "enum": [ + "all", + "pick" + ] + }, + "SubmissionRequirement": { + "properties": { + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "rule": { + "$ref": "#/components/schemas/Rules" + }, + "count": { + "type": "number", + "format": "double" + }, + "min": { + "type": "number", + "format": "double" + }, + "max": { + "type": "number", + "format": "double" + }, + "from": { + "type": "string" + }, + "from_nested": { + "items": { + "$ref": "#/components/schemas/SubmissionRequirement" + }, + "type": "array" + } + }, + "required": [ + "rule" + ], + "type": "object", + "additionalProperties": false + }, + "InputDescriptorV2": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/components/schemas/Format" + }, + "group": { + "items": { + "type": "string" + }, + "type": "array" + }, + "issuance": { + "items": { + "$ref": "#/components/schemas/Issuance" + }, + "type": "array" + }, + "constraints": { + "$ref": "#/components/schemas/ConstraintsV2" + } + }, + "required": [ + "id", + "constraints" + ], + "type": "object", + "additionalProperties": false + }, + "PresentationDefinitionV2": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/components/schemas/Format" + }, + "submission_requirements": { + "items": { + "$ref": "#/components/schemas/SubmissionRequirement" + }, + "type": "array" + }, + "input_descriptors": { + "items": { + "$ref": "#/components/schemas/InputDescriptorV2" + }, + "type": "array" + }, + "frame": { + "additionalProperties": false, + "type": "object" + } + }, + "required": [ + "id", + "input_descriptors" + ], + "type": "object", + "additionalProperties": false + }, + "DifPresentationExchangeDefinitionV2Model": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "format": { + "$ref": "#/components/schemas/Format" + }, + "submission_requirements": { + "items": {}, + "type": "array" + }, + "input_descriptors": { + "items": { + "$ref": "#/components/schemas/InputDescriptorV2Model" + }, + "type": "array" + }, + "frame": { + "additionalProperties": false, + "type": "object" + } + }, + "required": [ + "id", + "input_descriptors" + ], + "type": "object", + "additionalProperties": false + }, + "PresentationDefinition": { + "properties": { + "definition": { + "$ref": "#/components/schemas/DifPresentationExchangeDefinitionV2Model" + } + }, + "required": [ + "definition" + ], + "type": "object", + "additionalProperties": false + }, + "DcqlClaim": { + "properties": { + "path": { + "items": { + "type": "string" + }, + "type": "array" + }, + "intent_to_retain": { + "type": "boolean" + } + }, + "required": [ + "path" + ], + "type": "object", + "additionalProperties": false + }, + "DcqlCredential": { + "properties": { + "id": { + "type": "string" + }, + "format": { + "type": "string" + }, + "meta": { + "$ref": "#/components/schemas/Record_string.any_" + }, + "require_cryptographic_holder_binding": { + "type": "boolean" + }, + "claims": { + "items": { + "$ref": "#/components/schemas/DcqlClaim" + }, + "type": "array" + } + }, + "required": [ + "id", + "format", + "claims" + ], + "type": "object", + "additionalProperties": false + }, + "DcqlQuery": { + "properties": { + "combine": { + "type": "string", + "enum": [ + "all", + "any" + ] + }, + "credentials": { + "items": { + "$ref": "#/components/schemas/DcqlCredential" + }, + "type": "array" + } + }, + "required": [ + "credentials" ], "type": "object", "additionalProperties": false }, - "TenantConfig": { + "DcqlDefinition": { "properties": { - "label": { - "type": "string" + "query": { + "$ref": "#/components/schemas/DcqlQuery" } }, "required": [ - "label" + "query" ], - "type": "object" - }, - "MetadataValue": { - "$ref": "#/components/schemas/Record_string.any_" + "type": "object", + "additionalProperties": false }, - "MetadataBase": { - "properties": {}, - "additionalProperties": { - "$ref": "#/components/schemas/MetadataValue" - }, - "type": "object" + "ResponseModeEnum": { + "enum": [ + "direct_post", + "direct_post.jwt", + "dc_api", + "dc_api.jwt" + ], + "type": "string" }, - "Metadata____": { - "description": "Metadata access class to get, set (create and update), add (append to a record) and delete metadata on any record.\n\nset will override the previous value if it already exists\n\nnote: To add persistence to these records, you have to update the record in the correct repository", + "OpenId4VcJwtIssuerDid": { "properties": { - "data": { - "$ref": "#/components/schemas/MetadataBase" + "didUrl": { + "type": "string" + }, + "method": { + "type": "string", + "enum": [ + "did" + ], + "nullable": false } }, "required": [ - "data" + "didUrl", + "method" ], - "type": "object", - "additionalProperties": false + "type": "object" }, - "Pick_CustomTenantConfig.Exclude_keyofCustomTenantConfig.walletConfig__": { + "OpenId4VcIssuerX5c": { "properties": { - "label": { + "alg": { "type": "string" }, - "connectionImageUrl": { + "x5c": { + "items": { + "type": "string" + }, + "type": "array" + }, + "issuer": { "type": "string" + }, + "method": { + "type": "string", + "enum": [ + "x5c" + ], + "nullable": false } }, "required": [ - "label" + "x5c", + "method" ], - "type": "object", - "description": "From T, pick a set of properties whose keys are in the union K" - }, - "Omit_CustomTenantConfig.walletConfig_": { - "$ref": "#/components/schemas/Pick_CustomTenantConfig.Exclude_keyofCustomTenantConfig.walletConfig__", - "description": "Construct a type with the properties of T except for those in type K." + "type": "object" }, - "CreateTenantOptions": { + "CreateAuthorizationRequest": { "properties": { - "config": { - "$ref": "#/components/schemas/Omit_CustomTenantConfig.walletConfig_" + "verifierId": { + "type": "string" + }, + "presentationExchange": { + "$ref": "#/components/schemas/PresentationDefinition" + }, + "dcql": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/DcqlDefinition" + } + ] + }, + "responseMode": { + "$ref": "#/components/schemas/ResponseModeEnum" + }, + "requestSigner": { + "anyOf": [ + { + "$ref": "#/components/schemas/OpenId4VcJwtIssuerDid" + }, + { + "$ref": "#/components/schemas/OpenId4VcIssuerX5c" + } + ] + }, + "expectedOrigins": { + "items": { + "type": "string" + }, + "type": "array" } }, "required": [ - "config" + "verifierId", + "requestSigner" ], "type": "object", "additionalProperties": false + }, + "OpenId4VcVerificationSessionRecord": { + "$ref": "#/components/schemas/Record_string.unknown_" + }, + "OpenId4VcVerificationSessionState": { + "enum": [ + "RequestCreated", + "RequestUriRetrieved", + "ResponseVerified", + "Error" + ], + "type": "string" } }, "securitySchemes": { @@ -3822,6 +4518,193 @@ } ] } + }, + "/openid4vc/verification-sessions/create-presentation-request": { + "post": { + "operationId": "CreateProofRequest", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "description": "Create an authorization request, acting as a Relying Party (RP)", + "tags": [ + "oid4vc verification sessions" + ], + "security": [ + { + "jwt": [ + "tenant", + "dedicated" + ] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAuthorizationRequest" + } + } + } + } + } + }, + "/openid4vc/verification-sessions": { + "get": { + "operationId": "GetAllVerificationSessions", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/OpenId4VcVerificationSessionRecord" + }, + "type": "array" + } + } + } + } + }, + "description": "Retrieve all verification session records", + "tags": [ + "oid4vc verification sessions" + ], + "security": [ + { + "jwt": [ + "tenant", + "dedicated" + ] + } + ], + "parameters": [ + { + "in": "query", + "name": "publicVerifierId", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "payloadState", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "state", + "required": false, + "schema": { + "$ref": "#/components/schemas/OpenId4VcVerificationSessionState" + } + }, + { + "in": "query", + "name": "authorizationRequestUri", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "nonce", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/openid4vc/verification-sessions/{verificationSessionId}": { + "get": { + "operationId": "GetVerificationSessionsById", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenId4VcVerificationSessionRecord" + } + } + } + } + }, + "description": "Get verification session by ID", + "tags": [ + "oid4vc verification sessions" + ], + "security": [ + { + "jwt": [ + "tenant", + "dedicated" + ] + } + ], + "parameters": [ + { + "in": "path", + "name": "verificationSessionId", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, + "/openid4vc/verification-sessions/response/{verificationSessionId}": { + "get": { + "operationId": "GetVerifiedAuthorizationResponse", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "tags": [ + "oid4vc verification sessions" + ], + "security": [ + { + "jwt": [ + "tenant", + "dedicated" + ] + } + ], + "parameters": [ + { + "in": "path", + "name": "verificationSessionId", + "required": true, + "schema": { + "type": "string" + } + } + ] + } } }, "servers": [ diff --git a/src/utils/oid4vc-agent.ts b/src/utils/oid4vc-agent.ts index 644d8870..15597802 100644 --- a/src/utils/oid4vc-agent.ts +++ b/src/utils/oid4vc-agent.ts @@ -1,6 +1,7 @@ // FIXME: We've made many changes in this file for building agent with OIDC modules, please check the types and proper implementation of the changes import type { DisclosureFrame } from '../controllers/types' +import type { SdJwtVcHolderBinding } from '@credo-ts/core' import type { OpenId4VcCredentialHolderBinding, OpenId4VcCredentialHolderDidBinding, @@ -10,7 +11,7 @@ import type { OpenId4VciSignW3cCredentials, } from '@credo-ts/openid4vc' -import { DidsApi, SdJwtVcHolderBinding, X509Certificate, X509Service } from '@credo-ts/core' +import { DidsApi, Kms, MdocApi, X509Certificate, X509Service } from '@credo-ts/core' import { ClaimFormat, CredoError, @@ -26,51 +27,220 @@ import { OpenId4VciCredentialFormatProfile } from '@credo-ts/openid4vc' import { SignerMethod } from '../enums/enum' -export function getCredentialRequestToCredentialMapper(): OpenId4VciCredentialRequestToCredentialMapper { - return async (options) => { - const { - holderBinding, - issuanceSession, - verification, - credentialConfigurationId, - credentialConfiguration, - agentContext, - authorization, - credentialRequest, - credentialOffer, - } = options +// export function getCredentialRequestToCredentialMapper(): OpenId4VciCredentialRequestToCredentialMapper { +// return async (options) => { +// const { +// holderBinding, +// issuanceSession, +// verification, +// credentialConfigurationId, +// credentialConfiguration, +// agentContext, +// authorization, +// credentialRequest, +// credentialOffer, +// } = options - const issuanceMetadata = issuanceSession.issuanceMetadata - const issuerDid = issuanceMetadata?.['issuerDid'] as string | undefined - const issuerx509certificate = issuanceMetadata?.['issuerx509certificate'] as string[] | undefined +// const issuanceMetadata = issuanceSession.issuanceMetadata +// const issuerDid = issuanceMetadata?.['issuerDid'] as string | undefined +// const issuerx509certificate = issuanceMetadata?.['issuerx509certificate'] as string[] | undefined - if (!issuerDid && !issuerx509certificate) { - throw new Error('Either issuerDid or issuerx509certificate must be provided') - } +// if (!issuerDid && !issuerx509certificate) { +// throw new Error('Either issuerDid or issuerx509certificate must be provided') +// } - let issuerDidUrl: string | undefined = '' - if (issuerDid) { - const didsApi = await agentContext.dependencyManager.resolve(DidsApi) - const didDocument = await didsApi.resolveDidDocument(issuerDid) +// let issuerDidUrl: string | undefined = '' +// if (issuerDid) { +// const didsApi = await agentContext.dependencyManager.resolve(DidsApi) +// const didDocument = await didsApi.resolveDidDocument(issuerDid) - // Set the first verificationMethod as backup, in case we won't find a match - if (!issuerDidUrl && didDocument.verificationMethod?.[0].id) { - issuerDidUrl = didDocument.verificationMethod?.[0].id - } - } +// // Set the first verificationMethod as backup, in case we won't find a match +// if (!issuerDidUrl && didDocument.verificationMethod?.[0].id) { +// issuerDidUrl = didDocument.verificationMethod?.[0].id +// } +// } - if (!issuerDidUrl && !issuerx509certificate) { - throw new Error('No matching verification method found') - } +// if (!issuerDidUrl && !issuerx509certificate) { +// throw new Error('No matching verification method found') +// } + +// if (!issuanceMetadata?.['credentials']) throw new Error('credential payload is not provided') + +// const allCredentialPayload = issuanceMetadata?.['credentials'] + +// // Returns an array of all matching credentials +// const credentialPayload = Array.isArray(allCredentialPayload) +// ? allCredentialPayload.filter((c) => c.credentialSupportedId === credentialConfigurationId) +// : [] + +// if (credentialConfigurationId === 'PresentationAuthorization') { +// const trustedCertificates = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates +// if (trustedCertificates?.length !== 1) { +// throw new Error(`Expected exactly one trusted certificate. Received ${trustedCertificates?.length}.`) +// } + +// // For PresentationAuthorization, use JWK binding +// if (holderBinding.bindingMethod !== 'jwk') { +// throw new Error('PresentationAuthorization requires JWK binding') +// } + +// return { +// format: ClaimFormat.SdJwtDc, +// credentials: [ +// { +// payload: { +// vct: credentialConfiguration.vct, +// authorized_user: authorization.accessToken.payload.sub, +// }, +// holder: { +// method: 'jwk', +// jwk: holderBinding.keys[0].jwk, +// } as SdJwtVcHolderBinding, +// issuer: { +// method: 'x5c', +// x5c: trustedCertificates.map((cert) => X509Certificate.fromEncodedCertificate(cert)), +// issuer: 'ISSUER_HOST', +// }, +// }, +// ], +// type: 'credentials', +// } satisfies OpenId4VciSignSdJwtCredentials +// } + +// if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { +// if (holderBinding.bindingMethod !== 'did') { +// throw new Error('JwtVcJson requires DID binding') +// } + +// const didKey = holderBinding.keys[0] +// if (didKey.method !== 'did') { +// throw new Error('Expected DID binding method') +// } + +// return { +// format: ClaimFormat.JwtVc, +// credentials: [ +// { +// credential: new W3cCredential({ +// type: credentialConfiguration.credential_definition.type, +// issuer: new W3cIssuer({ +// id: parseDid(issuerDidUrl ?? '').did, +// }), +// credentialSubject: JsonTransformer.fromJSON( +// { +// id: parseDid(didKey.didUrl).did, +// claims: { +// ...credentialPayload[0]?.payload, +// }, +// }, +// W3cCredentialSubject +// ), +// issuanceDate: w3cDate(Date.now()), +// }), +// verificationMethod: issuerDidUrl ?? '', +// }, +// ], +// type: 'credentials', +// } satisfies OpenId4VciSignW3cCredentials +// } +// if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.SdJwtVc) { +// const disclosureFramePayload = +// credentialPayload[0]?.disclosureFrame && Object.keys(credentialPayload[0].disclosureFrame).length > 0 +// ? credentialPayload[0].disclosureFrame +// : {} + +// const holder = +// holderBinding.bindingMethod === 'did' +// ? { +// method: 'did' as const, +// didUrl: holderBinding.keys[0].method === 'did' ? holderBinding.keys[0].didUrl : '', +// } +// : { +// method: 'jwk' as const, +// jwk: holderBinding.keys[0].method === 'jwk' ? holderBinding.keys[0].jwk : {}, +// } + +// return { +// format: ClaimFormat.SdJwtDc, +// credentials: [ +// { +// payload: credentialPayload[0]?.payload, +// holder: holder as SdJwtVcHolderBinding, +// issuer: issuerDidUrl +// ? { +// method: 'did', +// didUrl: issuerDidUrl, +// } +// : { +// method: 'x5c', +// x5c: (issuerx509certificate ?? []).map((cert) => X509Certificate.fromEncodedCertificate(cert)), +// issuer: process.env.AGENT_HOST ?? 'http://localhost:4001', +// }, +// disclosureFrame: disclosureFramePayload, +// }, +// ], +// type: 'credentials', +// } satisfies OpenId4VciSignSdJwtCredentials +// } +// throw new Error('Invalid request') +// } +// } + +export function getMixedCredentialRequestToCredentialMapper(): OpenId4VciCredentialRequestToCredentialMapper { + return async ({ + holderBinding, + issuanceSession, + verification, + credentialConfigurationId, + credentialConfiguration, + // credentialConfigurationId, + // credentialConfigurationsSupported, + agentContext, + authorization, + }) => { + const issuanceMetadata = issuanceSession.issuanceMetadata if (!issuanceMetadata?.['credentials']) throw new Error('credential payload is not provided') const allCredentialPayload = issuanceMetadata?.['credentials'] + //const credentialConfigurationId = credentialConfigurationIds[0] // Returns an array of all matching credentials const credentialPayload = Array.isArray(allCredentialPayload) - ? allCredentialPayload.filter((c) => c.credentialSupportedId === credentialConfigurationId) + ? allCredentialPayload.filter( + (c: Record) => c.credentialSupportedId === credentialConfigurationId, + ) : [] + const credentialConfigurationSupported = credentialConfiguration[credentialConfigurationId] + if (credentialPayload.length === 0) { + throw new Error(`No credential payload found for credentialConfigurationId: ${credentialConfigurationId}`) + } + const credential = credentialPayload[0] + let issuerDidVerificationMethod: string | undefined = '' + let issuerx509certificate: string[] | undefined + + if (credential.signerOptions.method === SignerMethod.Did) { + if (credential.signerOptions.did) { + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const didDocument = await didsApi.resolveDidDocument(credential.signerOptions.did) + // Set the first verificationMethod as backup, in case we won't find a match + if (didDocument.verificationMethod?.[0].id) { + issuerDidVerificationMethod = didDocument.verificationMethod?.[0].id + } + + if (!issuerDidVerificationMethod) { + throw new Error('No matching verification method found') + } + } + } else if (credential.signerOptions.method === SignerMethod.X5c) { + if (credential.signerOptions.x5c) { + issuerx509certificate = credential.signerOptions.x5c // as string[] | undefined; + + if (!issuerx509certificate) { + throw new Error('x509certificate must be provided when using x5c as signer method') + } + } + } if (credentialConfigurationId === 'PresentationAuthorization') { const trustedCertificates = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates @@ -78,12 +248,8 @@ export function getCredentialRequestToCredentialMapper(): OpenId4VciCredentialRe throw new Error(`Expected exactly one trusted certificate. Received ${trustedCertificates?.length}.`) } - // For PresentationAuthorization, use JWK binding - if (holderBinding.bindingMethod !== 'jwk') { - throw new Error('PresentationAuthorization requires JWK binding') - } - return { + //credentialConfigurationId, format: ClaimFormat.SdJwtDc, credentials: [ { @@ -103,273 +269,153 @@ export function getCredentialRequestToCredentialMapper(): OpenId4VciCredentialRe }, ], type: 'credentials', + // credentials: holderBindings.map((holderBinding) => ({ + // payload: { + // vct: credentialConfigurationSupported.vct, + // authorized_user: authorization.accessToken.payload.sub, + // }, + // holder: holderBinding, + // issuer: + // holderBindings[0].method === 'did' + // ? { + // method: 'did', + // didUrl: issuerDidVerificationMethod ?? '', + // } + // : { method: 'x5c', x5c: trustedCertificates, issuer: 'ISSUER_HOST ' }, + // })), } satisfies OpenId4VciSignSdJwtCredentials } - if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { - if (holderBinding.bindingMethod !== 'did') { - throw new Error('JwtVcJson requires DID binding') - } + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.MsoMdoc) { + if (!issuerx509certificate) + throw new Error( + `issuerx509certificate is not provided for credential type ${OpenId4VciCredentialFormatProfile.MsoMdoc}`, + ) - const didKey = holderBinding.keys[0] - if (didKey.method !== 'did') { - throw new Error('Expected DID binding method') + if (!credentialConfiguration.doctype) { + throw new Error( + `'doctype' not found in credential configuration, ${JSON.stringify(credentialConfigurationSupported, null, 2)}`, + ) } + // national id and ICAO default + const namespace = credentialConfiguration.doctype + const holder = + holderBinding.bindingMethod === 'did' + ? { + method: 'did' as const, + didUrl: holderBinding.keys[0].method === 'did' ? holderBinding.keys[0].didUrl : '', + } + : { + method: 'jwk' as const, + jwk: holderBinding.keys[0].method === 'jwk' ? holderBinding.keys[0].jwk : {}, + } + const parsedCertificate = X509Service.parseCertificate(agentContext, { + encodedCertificate: issuerx509certificate[0], + }) + parsedCertificate.publicJwk.keyId = credential.signerOptions.keyId return { - format: ClaimFormat.JwtVc, + type: 'credentials', + format: ClaimFormat.MsoMdoc, credentials: [ { - credential: new W3cCredential({ - type: credentialConfiguration.credential_definition.type, - issuer: new W3cIssuer({ - id: parseDid(issuerDidUrl ?? '').did, - }), - credentialSubject: JsonTransformer.fromJSON( - { - id: parseDid(didKey.didUrl).did, - claims: { - ...credentialPayload[0]?.payload, - }, - }, - W3cCredentialSubject - ), - issuanceDate: w3cDate(Date.now()), - }), - verificationMethod: issuerDidUrl ?? '', + issuerCertificate: parsedCertificate, + holderKey: holderBinding.keys[0].jwk, + ...credential.payload, + docType: credentialConfiguration.doctype, + namespaces: namespace, }, ], - type: 'credentials', - } satisfies OpenId4VciSignW3cCredentials + } satisfies OpenId4VciSignMdocCredentials } - if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.SdJwtVc) { + // if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { + // for (const holderBinding of holderBindings) { + // assertDidBasedHolderBinding(holderBinding) + // } + + // return { + // credentialConfigurationId, + // format: ClaimFormat.JwtVc, + // credentials: holderBindings.map((holderBinding) => { + // assertDidBasedHolderBinding(holderBinding) + + // const finalVC = { + // credential: new W3cCredential({ + // type: credentialConfiguration.credential_definition.type, + // issuer: new W3cIssuer({ + // id: parseDid(issuerDidVerificationMethod).did, + // }), + // credentialSubject: JsonTransformer.fromJSON( + // { + // id: parseDid(holderBinding.didUrl).did, + // claims: { + // ...credential.payload, + // }, + // }, + // W3cCredentialSubject, + // ), + // issuanceDate: w3cDate(Date.now()), + // }), + // verificationMethod: issuerDidVerificationMethod, + // } + // return finalVC + // }), + // } satisfies OpenId4VciSignW3cCredentials + // } + + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.SdJwtDc) { const disclosureFramePayload = - credentialPayload[0]?.disclosureFrame && Object.keys(credentialPayload[0].disclosureFrame).length > 0 + credentialPayload[0].disclosureFrame && Object.keys(credentialPayload[0].disclosureFrame).length > 0 ? credentialPayload[0].disclosureFrame : {} - + //Taking leaf certifcate from chain as issuer certificate, if not provided explicitly taking AGENT_HTTP_URL as issuer + let parsedCertificate: any + if (!issuerDidVerificationMethod && issuerx509certificate) { + parsedCertificate = X509Service.parseCertificate(agentContext, { + encodedCertificate: issuerx509certificate[0], + }) + } else if (!issuerDidVerificationMethod) { + throw new Error(`issuerx509certificate is not provided for credential ${credentialConfigurationId}`) + } const holder = holderBinding.bindingMethod === 'did' ? { - method: 'did' as const, - didUrl: holderBinding.keys[0].method === 'did' ? holderBinding.keys[0].didUrl : '', - } + method: 'did' as const, + didUrl: holderBinding.keys[0].method === 'did' ? holderBinding.keys[0].didUrl : '', + } : { - method: 'jwk' as const, - jwk: holderBinding.keys[0].method === 'jwk' ? holderBinding.keys[0].jwk : {}, - } - + method: 'jwk' as const, + jwk: holderBinding.keys[0].method === 'jwk' ? holderBinding.keys[0].jwk : {}, + } return { format: ClaimFormat.SdJwtDc, credentials: [ { payload: credentialPayload[0]?.payload, holder: holder as SdJwtVcHolderBinding, - issuer: issuerDidUrl + issuer: issuerDidVerificationMethod ? { - method: 'did', - didUrl: issuerDidUrl, - } + method: 'did', + didUrl: issuerDidVerificationMethod, + } : { - method: 'x5c', - x5c: (issuerx509certificate ?? []).map((cert) => X509Certificate.fromEncodedCertificate(cert)), - issuer: process.env.AGENT_HOST ?? 'http://localhost:4001', - }, + method: 'x5c', + x5c: (issuerx509certificate ?? []).map((cert) => X509Certificate.fromEncodedCertificate(cert)), + // TODO: Need to check validation for issuer value + // issuer: process.env.AGENT_HOST ?? 'http://localhost:4001', + }, disclosureFrame: disclosureFramePayload, }, ], type: 'credentials', } satisfies OpenId4VciSignSdJwtCredentials } + throw new Error('Invalid request') } } -// export function getMixedCredentialRequestToCredentialMapper(): OpenId4VciCredentialRequestToCredentialMapper { -// return async ({ -// holderBindings, -// issuanceSession, -// verification, -// credentialConfigurationIds, -// credentialConfigurationsSupported, -// agentContext, -// authorization, -// }) => { -// const issuanceMetadata = issuanceSession.issuanceMetadata - -// if (!issuanceMetadata?.['credentials']) throw new Error('credential payload is not provided') - -// const allCredentialPayload = issuanceMetadata?.['credentials'] - -// const credentialConfigurationId = credentialConfigurationIds[0] - -// // Returns an array of all matching credentials -// const credentialPayload = Array.isArray(allCredentialPayload) -// ? allCredentialPayload.filter( -// (c: Record) => c.credentialSupportedId === credentialConfigurationId, -// ) -// : [] -// const credentialConfiguration = credentialConfigurationsSupported[credentialConfigurationId] - -// const credential = credentialPayload[0] - -// let issuerDidVerificationMethod: string | undefined = '' -// let issuerx509certificate: string[] | undefined - -// if (credential.signerOptions.method === SignerMethod.Did) { -// if (credential.signerOptions.did) { -// const didsApi = agentContext.dependencyManager.resolve(DidsApi) -// const didDocument = await didsApi.resolveDidDocument(credential.signerOptions.did) - -// // Set the first verificationMethod as backup, in case we won't find a match -// if (didDocument.verificationMethod?.[0].id) { -// issuerDidVerificationMethod = didDocument.verificationMethod?.[0].id -// } - -// if (!issuerDidVerificationMethod) { -// throw new Error('No matching verification method found') -// } -// } -// } else if (credential.signerOptions.method === SignerMethod.X5c) { -// if (credential.signerOptions.x5c) { -// issuerx509certificate = credential.signerOptions.x5c // as string[] | undefined; - -// if (!issuerx509certificate) { -// throw new Error('x509certificate must be provided when using x5c as signer method') -// } -// } -// } - -// if (credentialConfigurationId === 'PresentationAuthorization') { -// const trustedCertificates = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates -// if (trustedCertificates?.length !== 1) { -// throw new Error(`Expected exactly one trusted certificate. Received ${trustedCertificates?.length}.`) -// } - -// return { -// credentialConfigurationId, -// format: ClaimFormat.SdJwtVc, -// credentials: holderBindings.map((holderBinding) => ({ -// payload: { -// vct: credentialConfiguration.vct, -// authorized_user: authorization.accessToken.payload.sub, -// }, -// holder: holderBinding, -// issuer: -// holderBindings[0].method === 'did' -// ? { -// method: 'did', -// didUrl: issuerDidVerificationMethod ?? '', -// } -// : { method: 'x5c', x5c: trustedCertificates, issuer: 'ISSUER_HOST ' }, -// })), -// } satisfies OpenId4VciSignSdJwtCredentials -// } - -// if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.MsoMdoc) { -// if (!issuerx509certificate) -// throw new Error( -// `issuerx509certificate is not provided for credential type ${OpenId4VciCredentialFormatProfile.MsoMdoc}`, -// ) - -// if (!credentialConfiguration.doctype) { -// throw new Error( -// `'doctype' not found in credential configuration, ${JSON.stringify(credentialConfiguration, null, 2)}`, -// ) -// } - -// // national id and ICAO default -// const namespace = credentialConfiguration.doctype - -// return { -// credentialConfigurationId, -// format: ClaimFormat.MsoMdoc, -// credentials: holderBindings.map((holderBinding) => ({ -// issuerCertificate: issuerx509certificate[0], -// holderKey: holderBinding.key, -// ...credential.payload, -// docType: credentialConfiguration.doctype, -// })), -// } satisfies OpenId4VciSignMdocCredentials -// } - -// if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { -// for (const holderBinding of holderBindings) { -// assertDidBasedHolderBinding(holderBinding) -// } - -// return { -// credentialConfigurationId, -// format: ClaimFormat.JwtVc, -// credentials: holderBindings.map((holderBinding) => { -// assertDidBasedHolderBinding(holderBinding) - -// const finalVC = { -// credential: new W3cCredential({ -// type: credentialConfiguration.credential_definition.type, -// issuer: new W3cIssuer({ -// id: parseDid(issuerDidVerificationMethod).did, -// }), -// credentialSubject: JsonTransformer.fromJSON( -// { -// id: parseDid(holderBinding.didUrl).did, -// claims: { -// ...credential.payload, -// }, -// }, -// W3cCredentialSubject, -// ), -// issuanceDate: w3cDate(Date.now()), -// }), -// verificationMethod: issuerDidVerificationMethod, -// } -// return finalVC -// }), -// } satisfies OpenId4VciSignW3cCredentials -// } - -// if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.SdJwtVc) { -// const disclosureFramePayload = -// credentialPayload[0].disclosureFrame && Object.keys(credentialPayload[0].disclosureFrame).length > 0 -// ? credentialPayload[0].disclosureFrame -// : {} - -// //Taking leaf certifcate from chain as issuer certificate, if not provided explicitly taking AGENT_HTTP_URL as issuer -// let parsedCertificate: any -// if (!issuerDidVerificationMethod && issuerx509certificate) { -// parsedCertificate = X509Service.parseCertificate(agentContext, { -// encodedCertificate: issuerx509certificate[0], -// }) -// } else if (!issuerDidVerificationMethod) { -// throw new Error(`issuerx509certificate is not provided for credential ${credentialConfigurationId}`) -// } - -// return { -// credentialConfigurationId, -// format: ClaimFormat.SdJwtVc, -// credentials: holderBindings.map((holderBinding) => ({ -// payload: credential.payload, -// holder: holderBinding, -// issuer: issuerDidVerificationMethod -// ? { -// method: 'did', -// didUrl: issuerDidVerificationMethod, -// } -// : { -// method: 'x5c', -// x5c: issuerx509certificate ?? [], -// issuer: parsedCertificate.sanUriNames[0], -// }, -// disclosureFrame: disclosureFramePayload, -// })), -// } satisfies OpenId4VciSignSdJwtCredentials -// } - -// throw new Error('Invalid request') -// } -// } - function assertDidBasedHolderBinding( holderBinding: OpenId4VcCredentialHolderBinding, ): asserts holderBinding is OpenId4VcCredentialHolderDidBinding { @@ -437,17 +483,17 @@ function assertDidBasedHolderBinding( export async function getTrustedCerts() { try { const response = await fetch(`${process.env.TRUST_LIST_URL}`) - if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } - const data = await response.json() - console.log('Success:', data) return data as string[] + // return [ + // 'MIICBzCCAbmgAwIBAgIRAMEUex+GR2GZKusP/izv/oswBQYDK2VwMCsxHDAaBgNVBAMTE0V4YW1wbGUgQ29ycG9yYXRpb24xCzAJBgNVBAYTAlVTMB4XDTI1MDEwMTAwMDAwMFoXDTI3MDEwMTAwMDAwMFowKzEcMBoGA1UEAxMTRXhhbXBsZSBDb3Jwb3JhdGlvbjELMAkGA1UEBhMCVVMwKjAFBgMrZXADIQC5RNVbJCX2L/z/PLbvaxLqi7+hA4fUUStWcmAo/qkX2aOB8TCB7jAdBgNVHQ4EFgQUkog6trQXXfsjb472jybLCBixSAMwDgYDVR0PAQH/BAQDAgGiMBUGA1UdJQEB/wQLMAkGByiBjF0FAQIwIgYDVR0jAQH/BBgwFoAUkog6trQXXfsjb472jybLCBixSAMwQAYDVR0SAQH/BDYwNIILZXhhbXBsZS5jb22GEmh0dHA6Ly9leGFtcGxlLmNvbYERYWRtaW5AZXhhbXBsZS5jb20wLAYDVR0RAQH/BCIwIIILZXhhbXBsZS5jb22BEWFkbWluQGV4YW1wbGUuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAwBQYDK2VwA0EAPjHj2keDv8BN3FGkOqG36VQKaYvb9Ena+1BI7hb+sBJ+QgBTlj1sK/+I7LMUfu/K3oCyZxT1CZpYRZkh7GEWAw==', + // ] } catch (error) { + // eslint-disable-next-line no-console console.error('Error fetching data:', error) return [] } } -