diff --git a/src/controllers/multi-tenancy/MultiTenancyController.ts b/src/controllers/multi-tenancy/MultiTenancyController.ts index 1383c773..c79510c2 100644 --- a/src/controllers/multi-tenancy/MultiTenancyController.ts +++ b/src/controllers/multi-tenancy/MultiTenancyController.ts @@ -1918,37 +1918,6 @@ export class MultiTenancyController extends Controller { } } - - /** - * Sign data using a key - * - * @param tenantId Tenant identifier - * @param request Sign options - * data - Data has to be in base64 format - * publicKeyBase58 - Public key in base58 format - * @returns Signature in base64 format - */ - @Security('apiKey') - @Post('/sign/:tenantId') - public async sign(@Path('tenantId') tenantId: string, @Body() request: SignDataOptions) { - try { - const signature = await this.agent.modules.tenants.withTenantAgent({ tenantId }, async (tenantAgent) => { - // assertAskarWallet(tenantAgent.context.wallet) - - // tenantAgent.w3cCredentials - - const signature = await tenantAgent.context.wallet.sign({ - data: TypedArrayEncoder.fromBase64(request.data), - key: Key.fromPublicKeyBase58(request.publicKeyBase58, request.keyType), - }) - return TypedArrayEncoder.toBase64(signature) - }) - return signature - } catch (error) { - throw ErrorHandlingService.handle(error) - } - } - /** * Verify data using a key * @@ -1983,21 +1952,66 @@ export class MultiTenancyController extends Controller { public async signCredential( @Path('tenantId') tenantId: string, @Query('storeCredential') storeCredential: boolean, - @Body() credentialToSign: W3cJsonLdSignCredentialOptions | any + @Query('dataTypeToSign') dataTypeToSign: 'rawData' | 'jsonLd', + @Body() data: CustomW3cJsonLdSignCredentialOptions | SignDataOptions | any ) { - let storedCredential - let formattedCredential try { - await this.agent.modules.tenants.withTenantAgent({ tenantId }, async (tenantAgent) => { - credentialToSign.format = ClaimFormat.LdpVc + return await this.agent.modules.tenants.withTenantAgent({ tenantId }, async (tenantAgent) => { + // JSON-LD VC Signing + if (dataTypeToSign === 'jsonLd') { + const credentialData = data as unknown as W3cJsonLdSignCredentialOptions + credentialData.format = ClaimFormat.LdpVc + + const signedCredential = await tenantAgent.w3cCredentials.signCredential(credentialData) as W3cJsonLdVerifiableCredential - const signedCred = await tenantAgent.w3cCredentials.signCredential(credentialToSign) as W3cJsonLdVerifiableCredential - formattedCredential = signedCred.toJson() if (storeCredential) { - storedCredential = await tenantAgent.w3cCredentials.storeCredential({ credential: signedCred }) + return await tenantAgent.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 tenantAgent.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 tenantAgent.context.wallet.sign({ + data: TypedArrayEncoder.fromBase64(rawData.data), + key: keyToUse, + }) + + return TypedArrayEncoder.toBase64(signature) }) - return storedCredential ?? formattedCredential } catch (error) { throw ErrorHandlingService.handle(error) } diff --git a/src/controllers/types.ts b/src/controllers/types.ts index f3acb079..227d94a0 100644 --- a/src/controllers/types.ts +++ b/src/controllers/types.ts @@ -28,6 +28,9 @@ import type { JsonObject, W3cJsonLdVerifyCredentialOptions, DataIntegrityProofOptions, + W3cJsonLdSignCredentialOptions, + W3cCredential, + W3cCredentialSubject, } from '@credo-ts/core' import type { SingleOrArray } from '@credo-ts/core/build/utils' import type { DIDDocument } from 'did-resolver' @@ -394,6 +397,8 @@ export type SignDataOptions = { data: string keyType: KeyType publicKeyBase58: string + did?: string + method?: string } export type VerifyDataOptions = { @@ -418,4 +423,17 @@ export interface credentialPayloadToSign { export interface SafeW3cJsonLdVerifyCredentialOptions extends W3cJsonLdVerifyCredentialOptions { // Ommited due to issues with TSOA proof: SingleOrArray | DataIntegrityProofOptions> -} \ No newline at end of file +} + +export type ExtensibleW3cCredentialSubject = W3cCredentialSubject & { + [key: string]: unknown +} + +export type ExtensibleW3cCredential = W3cCredential & { + [key: string]: unknown + credentialSubject: SingleOrArray +} + +export type CustomW3cJsonLdSignCredentialOptions = Omit & { + [key: string]: unknown +} diff --git a/src/routes/routes.ts b/src/routes/routes.ts index c2294d13..68fbc8c9 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -693,29 +693,14 @@ const models: TsoaRoute.Models = { "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"content":{"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 - "SignDataOptions": { - "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"publicKeyBase58":{"dataType":"string","required":true},"keyType":{"ref":"KeyType","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 "VerifyDataOptions": { "dataType": "refAlias", "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"signature":{"dataType":"string","required":true},"publicKeyBase58":{"dataType":"string","required":true},"keyType":{"ref":"KeyType","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 - "ClaimFormat.LdpVc": { - "dataType": "refEnum", - "enums": ["ldp_vc"], - }, - // 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 - "ProofPurpose": { + "W3cCredentialRecord": { "dataType": "refAlias", - "type": {"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 - "ClaimFormat": { - "dataType": "refEnum", - "enums": ["jwt","jwt_vc","jwt_vp","ldp","ldp_vc","ldp_vp","di","di_vc","di_vp","vc+sd-jwt"], + "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 "W3cIssuer": { @@ -779,22 +764,29 @@ 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 - "W3cJsonLdSignCredentialOptions": { - "dataType": "refObject", - "properties": { - "format": {"ref":"ClaimFormat.LdpVc","required":true}, - "credential": {"ref":"W3cCredential","required":true}, - "verificationMethod": {"dataType":"string","required":true}, - "proofType": {"dataType":"string","required":true}, - "proofPurpose": {"ref":"ProofPurpose"}, - "created": {"dataType":"string"}, - }, - "additionalProperties": false, + "Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format__": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"credential":{"ref":"W3cCredential","required":true},"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_": { + "dataType": "refAlias", + "type": {"ref":"Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format__","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_"},{"dataType":"nestedObjectLiteral","nestedProperties":{},"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":{"ref":"KeyType","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 "Pick_LinkedDataProofOptions.Exclude_keyofLinkedDataProofOptions.cryptosuite__": { "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"type":{"dataType":"string","required":true},"proofPurpose":{"dataType":"string","required":true},"verificationMethod":{"dataType":"string","required":true},"created":{"dataType":"string","required":true},"domain":{"dataType":"string"},"challenge":{"dataType":"string"},"jws":{"dataType":"string"},"proofValue":{"dataType":"string"},"nonce":{"dataType":"string"}},"validators":{}}, + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"proofPurpose":{"dataType":"string","required":true},"created":{"dataType":"string","required":true},"verificationMethod":{"dataType":"string","required":true},"type":{"dataType":"string","required":true},"domain":{"dataType":"string"},"challenge":{"dataType":"string"},"jws":{"dataType":"string"},"proofValue":{"dataType":"string"},"nonce":{"dataType":"string"}},"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_LinkedDataProofOptions.cryptosuite_": { @@ -881,6 +873,11 @@ 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 + "ProofPurpose": { + "dataType": "refAlias", + "type": {"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 "SafeW3cJsonLdVerifyCredentialOptions": { "dataType": "refObject", "properties": { @@ -1170,11 +1167,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 "CredentialExchangeRecord": { "dataType": "refAlias", "type": {"ref":"Record_string.unknown_","validators":{}}, @@ -3745,43 +3737,6 @@ 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 - app.post('/multi-tenancy/sign/:tenantId', - authenticateMiddleware([{"apiKey":[]}]), - ...(fetchMiddlewares(MultiTenancyController)), - ...(fetchMiddlewares(MultiTenancyController.prototype.sign)), - - async function MultiTenancyController_sign(request: ExRequest, response: ExResponse, next: any) { - const args: Record = { - tenantId: {"in":"path","name":"tenantId","required":true,"dataType":"string"}, - request: {"in":"body","name":"request","required":true,"ref":"SignDataOptions"}, - }; - - // 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, request, response }); - - const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; - - const controller: any = await container.get(MultiTenancyController); - if (typeof controller['setStatus'] === 'function') { - controller.setStatus(undefined); - } - - await templateService.apiHandler({ - methodName: 'sign', - 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 app.post('/multi-tenancy/verify/:tenantId', authenticateMiddleware([{"apiKey":[]}]), ...(fetchMiddlewares(MultiTenancyController)), @@ -3828,7 +3783,8 @@ export function RegisterRoutes(app: Router) { const args: Record = { tenantId: {"in":"path","name":"tenantId","required":true,"dataType":"string"}, storeCredential: {"in":"query","name":"storeCredential","required":true,"dataType":"boolean"}, - credentialToSign: {"in":"body","name":"credentialToSign","required":true,"dataType":"union","subSchemas":[{"ref":"W3cJsonLdSignCredentialOptions"},{"dataType":"any"}]}, + dataTypeToSign: {"in":"query","name":"dataTypeToSign","required":true,"dataType":"union","subSchemas":[{"dataType":"enum","enums":["rawData"]},{"dataType":"enum","enums":["jsonLd"]}]}, + data: {"in":"body","name":"data","required":true,"dataType":"union","subSchemas":[{"ref":"CustomW3cJsonLdSignCredentialOptions"},{"ref":"SignDataOptions"},{"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 diff --git a/src/routes/swagger.json b/src/routes/swagger.json index c872bd20..d22382dd 100644 --- a/src/routes/swagger.json +++ b/src/routes/swagger.json @@ -1625,25 +1625,6 @@ "type": "object", "description": "Construct a type with a set of properties K of type T" }, - "SignDataOptions": { - "properties": { - "publicKeyBase58": { - "type": "string" - }, - "keyType": { - "$ref": "#/components/schemas/KeyType" - }, - "data": { - "type": "string" - } - }, - "required": [ - "publicKeyBase58", - "keyType", - "data" - ], - "type": "object" - }, "VerifyDataOptions": { "properties": { "signature": { @@ -1667,28 +1648,8 @@ ], "type": "object" }, - "ClaimFormat.LdpVc": { - "enum": [ - "ldp_vc" - ], - "type": "string" - }, - "ProofPurpose": {}, - "ClaimFormat": { - "description": "Defines the claim format based on the formats registered in\n[DIF Claim Format Registry](https://identity.foundation/claim-format-registry/).", - "enum": [ - "jwt", - "jwt_vc", - "jwt_vp", - "ldp", - "ldp_vc", - "ldp_vp", - "di", - "di_vc", - "di_vp", - "vc+sd-jwt" - ], - "type": "string" + "W3cCredentialRecord": { + "$ref": "#/components/schemas/Record_string.unknown_" }, "W3cIssuer": { "properties": { @@ -1832,52 +1793,86 @@ "type": "object", "additionalProperties": false }, - "W3cJsonLdSignCredentialOptions": { + "Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format__": { "properties": { - "format": { - "$ref": "#/components/schemas/ClaimFormat.LdpVc", - "description": "The format of the credential to be signed. Must be either `jwt_vc` or `ldp_vc`." - }, "credential": { "$ref": "#/components/schemas/W3cCredential", "description": "The credential to be signed." }, - "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." - }, "proofType": { "type": "string", "description": "The proofType to be used for signing the credential.\n\nMust be a valid Linked Data Signature suite." }, - "proofPurpose": { - "$ref": "#/components/schemas/ProofPurpose" - }, + "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": [ - "format", "credential", - "verificationMethod", - "proofType" + "proofType", + "verificationMethod" ], "type": "object", - "additionalProperties": false + "description": "From T, pick a set of properties whose keys are in the union K" }, - "Pick_LinkedDataProofOptions.Exclude_keyofLinkedDataProofOptions.cryptosuite__": { + "Omit_W3cJsonLdSignCredentialOptions.format_": { + "$ref": "#/components/schemas/Pick_W3cJsonLdSignCredentialOptions.Exclude_keyofW3cJsonLdSignCredentialOptions.format__", + "description": "Construct a type with the properties of T except for those in type K." + }, + "CustomW3cJsonLdSignCredentialOptions": { + "allOf": [ + { + "$ref": "#/components/schemas/Omit_W3cJsonLdSignCredentialOptions.format_" + }, + { + "properties": {}, + "additionalProperties": {}, + "type": "object" + } + ] + }, + "SignDataOptions": { "properties": { - "type": { + "method": { "type": "string" }, + "did": { + "type": "string" + }, + "publicKeyBase58": { + "type": "string" + }, + "keyType": { + "$ref": "#/components/schemas/KeyType" + }, + "data": { + "type": "string" + } + }, + "required": [ + "publicKeyBase58", + "keyType", + "data" + ], + "type": "object" + }, + "Pick_LinkedDataProofOptions.Exclude_keyofLinkedDataProofOptions.cryptosuite__": { + "properties": { "proofPurpose": { "type": "string" }, + "created": { + "type": "string" + }, "verificationMethod": { "type": "string" }, - "created": { + "type": { "type": "string" }, "domain": { @@ -1897,10 +1892,10 @@ } }, "required": [ - "type", "proofPurpose", + "created", "verificationMethod", - "created" + "type" ], "type": "object", "description": "From T, pick a set of properties whose keys are in the union K" @@ -2158,6 +2153,7 @@ "type": "object", "additionalProperties": false }, + "ProofPurpose": {}, "SafeW3cJsonLdVerifyCredentialOptions": { "properties": { "credential": { @@ -2826,9 +2822,6 @@ ], "type": "string" }, - "W3cCredentialRecord": { - "$ref": "#/components/schemas/Record_string.unknown_" - }, "CredentialExchangeRecord": { "$ref": "#/components/schemas/Record_string.unknown_" }, @@ -6892,55 +6885,6 @@ } } }, - "/multi-tenancy/sign/{tenantId}": { - "post": { - "operationId": "Sign", - "responses": { - "200": { - "description": "Signature in base64 format", - "content": { - "application/json": { - "schema": { - "type": "string" - } - } - } - } - }, - "description": "Sign data using a key", - "tags": [ - "MultiTenancy" - ], - "security": [ - { - "apiKey": [] - } - ], - "parameters": [ - { - "description": "Tenant identifier", - "in": "path", - "name": "tenantId", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Sign options\ndata - Data has to be in base64 format\npublicKeyBase58 - Public key in base58 format", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SignDataOptions", - "description": "Sign options\ndata - Data has to be in base64 format\npublicKeyBase58 - Public key in base58 format" - } - } - } - } - } - }, "/multi-tenancy/verify/{tenantId}": { "post": { "operationId": "Verify", @@ -6998,7 +6942,19 @@ "description": "Ok", "content": { "application/json": { - "schema": {} + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/Record_string.any_" + }, + { + "$ref": "#/components/schemas/W3cCredentialRecord" + } + ] + } } } } @@ -7027,6 +6983,18 @@ "schema": { "type": "boolean" } + }, + { + "in": "query", + "name": "dataTypeToSign", + "required": true, + "schema": { + "type": "string", + "enum": [ + "rawData", + "jsonLd" + ] + } } ], "requestBody": { @@ -7036,7 +7004,10 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/W3cJsonLdSignCredentialOptions" + "$ref": "#/components/schemas/CustomW3cJsonLdSignCredentialOptions" + }, + { + "$ref": "#/components/schemas/SignDataOptions" }, {} ]