diff --git a/src/controllers/agent/AgentController.ts b/src/controllers/agent/AgentController.ts index 891ad5a2..f7da7807 100644 --- a/src/controllers/agent/AgentController.ts +++ b/src/controllers/agent/AgentController.ts @@ -1,13 +1,31 @@ -import type { AgentInfo, AgentToken, SafeW3cJsonLdVerifyCredentialOptions } from '../types' +import type { + AgentInfo, + AgentToken, + SafeW3cJsonLdVerifyCredentialOptions, + CustomW3cJsonLdSignCredentialOptions, + SignDataOptions, + VerifyDataOptions, +} from '../types' -import { JsonTransformer, W3cJsonLdVerifiableCredential } from '@credo-ts/core' +import { + JsonTransformer, + W3cJsonLdVerifiableCredential, + TypedArrayEncoder, + ClaimFormat, + W3cCredentialRecord, + DidDocument, + verkeyToPublicJwk, + getKmsKeyIdForVerifiacationMethod, +} from '@credo-ts/core' import { Request as Req } from 'express' import jwt from 'jsonwebtoken' -import { Controller, Get, Route, Tags, Security, Request, Post, Body } from 'tsoa' +import { Controller, Get, Route, Tags, Security, Request, Post, Body, Query } from 'tsoa' import { injectable } from 'tsyringe' import { AgentRole, SCOPES } from '../../enums' import ErrorHandlingService from '../../errorHandlingService' +import { BadRequestError } from '../../errors/errors' +import { ALGORITHM_MAP } from '../../utils/constant' @Tags('Agent') @Route('/agent') @@ -54,108 +72,166 @@ export class AgentController extends Controller { } } - // /** - // * Delete wallet - // */ - // @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) - // @Delete('/wallet') - // public async deleteWallet(@Request() request: Req) { - // try { - // const deleteWallet = await request.agent.wallet.delete() - // return deleteWallet - // } catch (error) { - // throw ErrorHandlingService.handle(error) - // } - // } - - // /** - // * Verify data using a key - // * - // * @param tenantId Tenant identifier - // * @param request Verify options - // * data - Data has to be in base64 format - // * publicKeyBase58 - Public key in base58 format - // * signature - Signature in base64 format - // * @returns isValidSignature - true if signature is valid, false otherwise - // */ - // @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) - // @Post('/verify') - // public async verify(@Request() request: Req, @Body() body: VerifyDataOptions) { - // try { - // assertAskarWallet(request.agent.context.wallet) - // const isValidSignature = await request.agent.context.wallet.verify({ - // data: TypedArrayEncoder.fromBase64(body.data), - // key: Key.fromPublicKeyBase58(body.publicKeyBase58, body.keyType), - // signature: TypedArrayEncoder.fromBase64(body.signature), - // }) - // return isValidSignature - // } catch (error) { - // throw ErrorHandlingService.handle(error) - // } - // } - - // //Triage: Do we want the BW to be able to sign and verify as well? - // @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) - // @Post('/credential/sign') - // public async signCredential( - // @Request() request: Req, - // @Query('storeCredential') storeCredential: boolean, - // @Query('dataTypeToSign') dataTypeToSign: 'rawData' | 'jsonLd', - // @Body() data: CustomW3cJsonLdSignCredentialOptions | SignDataOptions | unknown, - // ) { - // try { - // // JSON-LD VC Signing - // if (dataTypeToSign === 'jsonLd') { - // const credentialData = data as unknown as W3cJsonLdSignCredentialOptions - // credentialData.format = ClaimFormat.LdpVc - // const signedCredential = (await request.agent.w3cCredentials.signCredential( - // credentialData, - // )) as W3cJsonLdVerifiableCredential - // if (storeCredential) { - // return await request.agent.w3cCredentials.storeCredential({ credential: signedCredential }) - // } - // return signedCredential.toJson() - // } - - // // Raw Data Signing - // const rawData = data as SignDataOptions - // if (!rawData.data) throw new BadRequestError('Missing "data" for raw data signing.') - - // const hasDidOrMethod = rawData.did || rawData.method - // const hasPublicKey = rawData.publicKeyBase58 && rawData.keyType - // if (!hasDidOrMethod && !hasPublicKey) { - // throw new BadRequestError('Either (did or method) OR (publicKeyBase58 and keyType) must be provided.') - // } - - // let keyToUse: Key - // if (hasDidOrMethod) { - // const dids = await request.agent.dids.getCreatedDids({ - // method: rawData.method || undefined, - // did: rawData.did || undefined, - // }) - // const verificationMethod = dids[0]?.didDocument?.verificationMethod?.[0]?.publicKeyBase58 - // if (!verificationMethod) { - // throw new BadRequestError('No publicKeyBase58 found for the given DID or method.') - // } - // keyToUse = Key.fromPublicKeyBase58(verificationMethod, rawData.keyType) - // } else { - // keyToUse = Key.fromPublicKeyBase58(rawData.publicKeyBase58, rawData.keyType) - // } - - // if (!keyToUse) { - // throw new Error('Unable to construct signing key. ') - // } - - // const signature = await request.agent.context.wallet.sign({ - // data: TypedArrayEncoder.fromBase64(rawData.data), - // key: keyToUse, - // }) - - // return TypedArrayEncoder.toBase64(signature) - // } catch (error) { - // throw ErrorHandlingService.handle(error) - // } - // } + /** + * Verify data using a key + * + * @param body Verify options + * data - Data has to be in base64 format + * publicKeyBase58 - Public key in base58 format + * signature - Signature in base64 format + * @returns isValidSignature - true if signature is valid, false otherwise + */ + @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) + @Post('/verify') + public async verify(@Request() request: Req, @Body() body: VerifyDataOptions): Promise<{ verified: boolean }> { + try { + if (!body.data || !body.signature || !body.publicKeyBase58 || !body.keyType) { + throw new BadRequestError( + 'Missing required fields: data, signature, publicKeyBase58, and keyType are required.', + ) + } + + // Convert verkey to JWK + const publicJwkWrapper = verkeyToPublicJwk(body.publicKeyBase58) + const publicJwk = (publicJwkWrapper as any).jwk?.jwk ?? (publicJwkWrapper as any).jwk ?? publicJwkWrapper + + const result = await request.agent.kms.verify({ + data: TypedArrayEncoder.fromBase64(body.data), + signature: TypedArrayEncoder.fromBase64(body.signature), + key: { + publicJwk: publicJwk as any, + }, + algorithm: (ALGORITHM_MAP[body.keyType.toLowerCase()] || body.keyType) as any, + }) + + return { verified: result.verified } + } catch (error) { + throw ErrorHandlingService.handle(error) + } + } + + /** + * Sign credential or raw data + */ + @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) + @Post('/credential/sign') + public async signCredential( + @Request() request: Req, + @Query('storeCredential') storeCredential: boolean, + @Query('dataTypeToSign') dataTypeToSign: 'rawData' | 'jsonLd', + @Body() body: CustomW3cJsonLdSignCredentialOptions | SignDataOptions, + ) { + try { + const typeToSign = (dataTypeToSign || 'rawData').toLowerCase() + request.agent.config.logger.info( + `[SignCredential] dataTypeToSign: ${dataTypeToSign}, typeToSign: ${typeToSign}, storeCredential: ${storeCredential}`, + ) + + if (typeToSign === 'jsonld') { + return await this.signJsonLd(request, body as CustomW3cJsonLdSignCredentialOptions, storeCredential) + } + + return await this.signRawData(request, body as SignDataOptions) + } catch (error) { + const err = error as any + request.agent.config.logger.error(`[SignCredential] Error: ${err.message}`, { stack: err.stack }) + throw ErrorHandlingService.handle(error) + } + } + + private async signJsonLd(request: Req, body: CustomW3cJsonLdSignCredentialOptions, storeCredential: boolean) { + const credentialData = body as any + + // Ensure signerOptions is populated if top-level fields are provided + if (!credentialData.signerOptions && (credentialData.verificationMethod || credentialData.proofType)) { + credentialData.signerOptions = { + verificationMethod: credentialData.verificationMethod, + type: credentialData.proofType, + method: 'did', // default to did if not specified + } + } + + credentialData.format = ClaimFormat.LdpVc + const signedCredential = (await request.agent.w3cCredentials.signCredential( + credentialData, + )) as W3cJsonLdVerifiableCredential + + if (storeCredential) { + const record = W3cCredentialRecord.fromCredential(signedCredential) + return await request.agent.w3cCredentials.store({ record }) + } + return signedCredential.toJson() + } + + private async signRawData(request: Req, body: SignDataOptions) { + if (!body.data) throw new BadRequestError('Missing "data" for raw data signing.') + + const kmsKeyId = await this.resolveKmsKeyId(request, body) + + const signature = await request.agent.kms.sign({ + data: TypedArrayEncoder.fromBase64(body.data), + keyId: kmsKeyId as string, + algorithm: (body.keyType ? ALGORITHM_MAP[body.keyType.toLowerCase()] || body.keyType : 'EdDSA') as any, + }) + + return TypedArrayEncoder.toBase64(signature.signature) + } + + private async resolveKmsKeyId(request: Req, body: SignDataOptions): Promise { + const hasDidOrMethod = body.did || body.method + const hasPublicKey = body.publicKeyBase58 && body.keyType + if (!hasDidOrMethod && !hasPublicKey) { + throw new BadRequestError('Either (did or method) OR (publicKeyBase58 and keyType) must be provided.') + } + + if (!hasDidOrMethod) { + return body.publicKeyBase58 + } + + let kmsKeyId: string | undefined = undefined + let didDocument: DidDocument | undefined | null = undefined + + const dids = await request.agent.dids.getCreatedDids({ + method: body.method || undefined, + did: body.did || undefined, + }) + + const didRecord = dids[0] + if (didRecord) { + didDocument = didRecord.didDocument + if (didRecord.keys && didRecord.keys.length > 0) { + kmsKeyId = didRecord.keys[0].kmsKeyId + } + } + + if (!didDocument && body.did) { + const resolution = await request.agent.dids.resolve(body.did) + didDocument = resolution.didDocument + } + + if (!didDocument) { + throw new BadRequestError('No DID document found.') + } + + if (!kmsKeyId) { + const verificationMethod = didDocument.verificationMethod?.[0] + if (!verificationMethod) { + throw new BadRequestError('No verification method found on DID document.') + } + + // Try multiple ways to get the kmsKeyId + const derivedKeyId = getKmsKeyIdForVerifiacationMethod(verificationMethod) + const publicKeyBase58 = (verificationMethod as any).publicKeyBase58 + const vmId = verificationMethod.id || '' + const idPart = vmId.includes('#') ? vmId.split('#')[1] : undefined + + kmsKeyId = (derivedKeyId || publicKeyBase58 || idPart || vmId) as string + + request.agent.config.logger.info(`[SignCredential] Resolved kmsKeyId via fallback: ${kmsKeyId}`) + } + + return kmsKeyId + } @Security('jwt', [SCOPES.TENANT_AGENT, SCOPES.DEDICATED_AGENT]) @Post('/credential/verify') diff --git a/src/controllers/types.ts b/src/controllers/types.ts index 1c5ef7b3..ce3b0a5a 100644 --- a/src/controllers/types.ts +++ b/src/controllers/types.ts @@ -464,7 +464,11 @@ export type ExtensibleW3cCredential = W3cCredential & { credentialSubject: SingleOrArray } -export type CustomW3cJsonLdSignCredentialOptions = Omit & { +export type CustomW3cJsonLdSignCredentialOptions = Omit & { + credential: Omit & { + '@context': Array> + credentialSubject: SingleOrArray + } [key: string]: unknown } diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 3285a6ec..11840794 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -919,7 +919,7 @@ const models: TsoaRoute.Models = { "W3cCredential": { "dataType": "refObject", "properties": { - "context": {"dataType":"array","array":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"JsonObject"}]},"required":true}, + "@context": {"dataType":"array","array":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"JsonObject"}]},"required":true}, "id": {"dataType":"string"}, "type": {"dataType":"array","array":{"dataType":"string"},"required":true}, "issuer": {"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"W3cIssuer"}],"required":true}, @@ -1428,6 +1428,86 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "VerifyDataOptions": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"signature":{"dataType":"string","required":true},"publicKeyBase58":{"dataType":"string","required":true},"keyType":{"dataType":"any","required":true},"data":{"dataType":"string","required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "W3cCredentialRecord": { + "dataType": "refAlias", + "type": {"ref":"Record_string.unknown_","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "W3cJsonIssuer": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + }, + "additionalProperties": {"dataType":"any"}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "W3cJsonCredentialSubject": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + }, + "additionalProperties": {"dataType":"any"}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SingleOrArray_W3cJsonCredentialSubject_": { + "dataType": "refAlias", + "type": {"dataType":"union","subSchemas":[{"ref":"W3cJsonCredentialSubject"},{"dataType":"array","array":{"dataType":"refObject","ref":"W3cJsonCredentialSubject"}}],"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "W3cJsonCredential": { + "dataType": "refObject", + "properties": { + "@context": {"dataType":"array","array":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"JsonObject"}]},"required":true}, + "id": {"dataType":"string"}, + "type": {"dataType":"array","array":{"dataType":"string"},"required":true}, + "issuer": {"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"W3cJsonIssuer"}],"required":true}, + "issuanceDate": {"dataType":"string","required":true}, + "expirationDate": {"dataType":"string"}, + "credentialSubject": {"ref":"SingleOrArray_W3cJsonCredentialSubject_","required":true}, + }, + "additionalProperties": {"dataType":"any"}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format-or-credential__": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"proofType":{"dataType":"string","required":true},"proofPurpose":{"dataType":"any"},"created":{"dataType":"string"},"verificationMethod":{"dataType":"string","required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Omit_W3cJsonLdSignCredentialOptions.format-or-credential_": { + "dataType": "refAlias", + "type": {"ref":"Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format-or-credential__","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Pick_W3cCredential.Exclude_keyofW3cCredential.context-or-credentialSubject__": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"issuer":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"W3cIssuer"}],"required":true},"type":{"dataType":"array","array":{"dataType":"string"},"required":true},"id":{"dataType":"string"},"@context":{"dataType":"array","array":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"JsonObject"}]},"required":true},"issuanceDate":{"dataType":"string","required":true},"expirationDate":{"dataType":"string"},"credentialSchema":{"ref":"SingleOrArray_W3cCredentialSchema_"},"credentialStatus":{"ref":"W3cCredentialStatus"}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Omit_W3cCredential.context-or-credentialSubject_": { + "dataType": "refAlias", + "type": {"ref":"Pick_W3cCredential.Exclude_keyofW3cCredential.context-or-credentialSubject__","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SingleOrArray_JsonObject_": { + "dataType": "refAlias", + "type": {"dataType":"union","subSchemas":[{"ref":"JsonObject"},{"dataType":"array","array":{"dataType":"refObject","ref":"JsonObject"}}],"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CustomW3cJsonLdSignCredentialOptions": { + "dataType": "refAlias", + "type": {"dataType":"intersection","subSchemas":[{"ref":"Omit_W3cJsonLdSignCredentialOptions.format-or-credential_"},{"dataType":"nestedObjectLiteral","nestedProperties":{"credential":{"dataType":"intersection","subSchemas":[{"ref":"Omit_W3cCredential.context-or-credentialSubject_"},{"dataType":"nestedObjectLiteral","nestedProperties":{"credentialSubject":{"ref":"SingleOrArray_JsonObject_","required":true},"@context":{"dataType":"array","array":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"Record_string.unknown_"}]},"required":true}}}],"required":true}},"additionalProperties":{"dataType":"any"}}],"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SignDataOptions": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"method":{"dataType":"string"},"did":{"dataType":"string"},"publicKeyBase58":{"dataType":"string","required":true},"keyType":{"dataType":"any","required":true},"data":{"dataType":"string","required":true}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Partial_W3cCredentialValidations_": { "dataType": "refAlias", "type": {"dataType":"nestedObjectLiteral","nestedProperties":{},"validators":{}}, @@ -1523,7 +1603,7 @@ const models: TsoaRoute.Models = { "W3cJsonLdVerifiableCredential": { "dataType": "refObject", "properties": { - "context": {"dataType":"array","array":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"JsonObject"}]},"required":true}, + "@context": {"dataType":"array","array":{"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"JsonObject"}]},"required":true}, "id": {"dataType":"string"}, "type": {"dataType":"array","array":{"dataType":"string"},"required":true}, "issuer": {"dataType":"union","subSchemas":[{"dataType":"string"},{"ref":"W3cIssuer"}],"required":true}, @@ -1747,11 +1827,6 @@ const models: TsoaRoute.Models = { "enums": ["issuer","holder"], }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "W3cCredentialRecord": { - "dataType": "refAlias", - "type": {"ref":"Record_string.unknown_","validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "DidCommCredentialExchangeRecord": { "dataType": "refAlias", "type": {"ref":"Record_string.unknown_","validators":{}}, @@ -1795,11 +1870,6 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "SingleOrArray_JsonObject_": { - "dataType": "refAlias", - "type": {"dataType":"union","subSchemas":[{"ref":"JsonObject"},{"dataType":"array","array":{"dataType":"refObject","ref":"JsonObject"}}],"validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "JsonCredential": { "dataType": "refObject", "properties": { @@ -4703,6 +4773,82 @@ export function RegisterRoutes(app: Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAgentController_verify: Record = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + body: {"in":"body","name":"body","required":true,"ref":"VerifyDataOptions"}, + }; + app.post('/agent/verify', + authenticateMiddleware([{"jwt":["tenant","dedicated"]}]), + ...(fetchMiddlewares(AgentController)), + ...(fetchMiddlewares(AgentController.prototype.verify)), + + async function AgentController_verify(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAgentController_verify, request, response }); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(AgentController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + await templateService.apiHandler({ + methodName: 'verify', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsAgentController_signCredential: Record = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + storeCredential: {"in":"query","name":"storeCredential","required":true,"dataType":"boolean"}, + dataTypeToSign: {"in":"query","name":"dataTypeToSign","required":true,"dataType":"union","subSchemas":[{"dataType":"enum","enums":["rawData"]},{"dataType":"enum","enums":["jsonLd"]}]}, + body: {"in":"body","name":"body","required":true,"dataType":"union","subSchemas":[{"ref":"CustomW3cJsonLdSignCredentialOptions"},{"ref":"SignDataOptions"}]}, + }; + app.post('/agent/credential/sign', + authenticateMiddleware([{"jwt":["tenant","dedicated"]}]), + ...(fetchMiddlewares(AgentController)), + ...(fetchMiddlewares(AgentController.prototype.signCredential)), + + async function AgentController_signCredential(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsAgentController_signCredential, request, response }); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(AgentController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + await templateService.apiHandler({ + methodName: 'signCredential', + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const argsAgentController_verifyCredential: Record = { request: {"in":"request","name":"request","required":true,"dataType":"object"}, credentialToVerify: {"in":"body","name":"credentialToVerify","required":true,"dataType":"union","subSchemas":[{"ref":"SafeW3cJsonLdVerifyCredentialOptions"},{"dataType":"any"}]}, diff --git a/src/routes/swagger.json b/src/routes/swagger.json index 4fceed67..7f02a7c9 100644 --- a/src/routes/swagger.json +++ b/src/routes/swagger.json @@ -2016,7 +2016,7 @@ }, "W3cCredential": { "properties": { - "context": { + "@context": { "items": { "anyOf": [ { @@ -2065,7 +2065,7 @@ } }, "required": [ - "context", + "@context", "type", "issuer", "issuanceDate", @@ -3242,6 +3242,288 @@ "type": "object", "additionalProperties": false }, + "VerifyDataOptions": { + "properties": { + "signature": { + "type": "string" + }, + "publicKeyBase58": { + "type": "string" + }, + "keyType": {}, + "data": { + "type": "string" + } + }, + "required": [ + "signature", + "publicKeyBase58", + "keyType", + "data" + ], + "type": "object" + }, + "W3cCredentialRecord": { + "$ref": "#/components/schemas/Record_string.unknown_" + }, + "W3cJsonIssuer": { + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "additionalProperties": {} + }, + "W3cJsonCredentialSubject": { + "properties": { + "id": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": {} + }, + "SingleOrArray_W3cJsonCredentialSubject_": { + "anyOf": [ + { + "$ref": "#/components/schemas/W3cJsonCredentialSubject" + }, + { + "items": { + "$ref": "#/components/schemas/W3cJsonCredentialSubject" + }, + "type": "array" + } + ] + }, + "W3cJsonCredential": { + "properties": { + "@context": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/JsonObject" + } + ] + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "items": { + "type": "string" + }, + "type": "array" + }, + "issuer": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/W3cJsonIssuer" + } + ] + }, + "issuanceDate": { + "type": "string" + }, + "expirationDate": { + "type": "string" + }, + "credentialSubject": { + "$ref": "#/components/schemas/SingleOrArray_W3cJsonCredentialSubject_" + } + }, + "required": [ + "@context", + "type", + "issuer", + "issuanceDate", + "credentialSubject" + ], + "type": "object", + "additionalProperties": {} + }, + "Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format-or-credential__": { + "properties": { + "proofType": { + "type": "string", + "description": "The proofType to be used for signing the credential.\n\nMust be a valid Linked Data Signature suite." + }, + "proofPurpose": {}, + "created": { + "type": "string" + }, + "verificationMethod": { + "type": "string", + "description": "URI of the verificationMethod to be used for signing the credential.\n\nMust be a valid did url pointing to a key." + } + }, + "required": [ + "proofType", + "verificationMethod" + ], + "type": "object", + "description": "From T, pick a set of properties whose keys are in the union K" + }, + "Omit_W3cJsonLdSignCredentialOptions.format-or-credential_": { + "$ref": "#/components/schemas/Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format-or-credential__", + "description": "Construct a type with the properties of T except for those in type K." + }, + "Pick_W3cCredential.Exclude_keyofW3cCredential.context-or-credentialSubject__": { + "properties": { + "issuer": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/W3cIssuer" + } + ] + }, + "type": { + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "@context": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/JsonObject" + } + ] + }, + "type": "array" + }, + "issuanceDate": { + "type": "string" + }, + "expirationDate": { + "type": "string" + }, + "credentialSchema": { + "$ref": "#/components/schemas/SingleOrArray_W3cCredentialSchema_" + }, + "credentialStatus": { + "$ref": "#/components/schemas/W3cCredentialStatus" + } + }, + "required": [ + "issuer", + "type", + "@context", + "issuanceDate" + ], + "type": "object", + "description": "From T, pick a set of properties whose keys are in the union K" + }, + "Omit_W3cCredential.context-or-credentialSubject_": { + "$ref": "#/components/schemas/Pick_W3cCredential.Exclude_keyofW3cCredential.context-or-credentialSubject__", + "description": "Construct a type with the properties of T except for those in type K." + }, + "SingleOrArray_JsonObject_": { + "anyOf": [ + { + "$ref": "#/components/schemas/JsonObject" + }, + { + "items": { + "$ref": "#/components/schemas/JsonObject" + }, + "type": "array" + } + ] + }, + "CustomW3cJsonLdSignCredentialOptions": { + "allOf": [ + { + "$ref": "#/components/schemas/Omit_W3cJsonLdSignCredentialOptions.format-or-credential_" + }, + { + "properties": { + "credential": { + "allOf": [ + { + "$ref": "#/components/schemas/Omit_W3cCredential.context-or-credentialSubject_" + }, + { + "properties": { + "credentialSubject": { + "$ref": "#/components/schemas/SingleOrArray_JsonObject_" + }, + "@context": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/Record_string.unknown_" + } + ] + }, + "type": "array" + } + }, + "required": [ + "credentialSubject", + "@context" + ], + "type": "object" + } + ] + } + }, + "additionalProperties": {}, + "required": [ + "credential" + ], + "type": "object" + } + ] + }, + "SignDataOptions": { + "properties": { + "method": { + "type": "string" + }, + "did": { + "type": "string" + }, + "publicKeyBase58": { + "type": "string" + }, + "keyType": {}, + "data": { + "type": "string" + } + }, + "required": [ + "publicKeyBase58", + "keyType", + "data" + ], + "type": "object" + }, "Partial_W3cCredentialValidations_": { "properties": {}, "type": "object", @@ -3474,7 +3756,7 @@ }, "W3cJsonLdVerifiableCredential": { "properties": { - "context": { + "@context": { "items": { "anyOf": [ { @@ -3526,7 +3808,7 @@ } }, "required": [ - "context", + "@context", "type", "issuer", "issuanceDate", @@ -4041,9 +4323,6 @@ ], "type": "string" }, - "W3cCredentialRecord": { - "$ref": "#/components/schemas/Record_string.unknown_" - }, "DidCommCredentialExchangeRecord": { "$ref": "#/components/schemas/Record_string.unknown_" }, @@ -4124,19 +4403,6 @@ "type": "object", "additionalProperties": false }, - "SingleOrArray_JsonObject_": { - "anyOf": [ - { - "$ref": "#/components/schemas/JsonObject" - }, - { - "items": { - "$ref": "#/components/schemas/JsonObject" - }, - "type": "array" - } - ] - }, "JsonCredential": { "properties": { "@context": { @@ -9057,6 +9323,134 @@ "parameters": [] } }, + "/agent/verify": { + "post": { + "operationId": "Verify", + "responses": { + "200": { + "description": "isValidSignature - true if signature is valid, false otherwise", + "content": { + "application/json": { + "schema": { + "properties": { + "verified": { + "type": "boolean" + } + }, + "required": [ + "verified" + ], + "type": "object" + } + } + } + } + }, + "description": "Verify data using a key", + "tags": [ + "Agent" + ], + "security": [ + { + "jwt": [ + "tenant", + "dedicated" + ] + } + ], + "parameters": [], + "requestBody": { + "description": "Verify options\ndata - Data has to be in base64 format\npublicKeyBase58 - Public key in base58 format\nsignature - Signature in base64 format", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VerifyDataOptions", + "description": "Verify options\ndata - Data has to be in base64 format\npublicKeyBase58 - Public key in base58 format\nsignature - Signature in base64 format" + } + } + } + } + } + }, + "/agent/credential/sign": { + "post": { + "operationId": "SignCredential", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/W3cCredentialRecord" + }, + { + "$ref": "#/components/schemas/W3cJsonCredential" + } + ] + } + } + } + } + }, + "description": "Sign credential or raw data", + "tags": [ + "Agent" + ], + "security": [ + { + "jwt": [ + "tenant", + "dedicated" + ] + } + ], + "parameters": [ + { + "in": "query", + "name": "storeCredential", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "dataTypeToSign", + "required": true, + "schema": { + "type": "string", + "enum": [ + "rawData", + "jsonLd" + ] + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/CustomW3cJsonLdSignCredentialOptions" + }, + { + "$ref": "#/components/schemas/SignDataOptions" + } + ] + } + } + } + } + } + }, "/agent/credential/verify": { "post": { "operationId": "VerifyCredential", diff --git a/src/utils/constant.ts b/src/utils/constant.ts index 179b75bf..9a53da47 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -29,3 +29,9 @@ export const curveToKty = { export const verkey = '#verkey' export const p521 = 'p521' export const STATUS_LISTS_PATH = 'status-lists' + +export const ALGORITHM_MAP: Record = { + ed25519: 'EdDSA', + p256: 'ES256', + secp256k1: 'ES256K', +}