diff --git a/__tests__/localAgent.test.ts b/__tests__/localAgent.test.ts index f82816b99..395918524 100644 --- a/__tests__/localAgent.test.ts +++ b/__tests__/localAgent.test.ts @@ -7,9 +7,7 @@ * This suite also runs a ganache local blockchain to run through some examples of DIDComm using did:ethr identifiers. */ -import { - createAgent, -} from '../packages/core/src' +import { createAgent } from '../packages/core/src' import { IAgentOptions, ICredentialPlugin, @@ -91,8 +89,9 @@ import didCommWithEthrDidFlow from './shared/didCommWithEthrDidFlow' import utils from './shared/utils' import web3 from './shared/web3' import credentialStatus from './shared/credentialStatus' -import ethrDidFlowSigned from "./shared/ethrDidFlowSigned"; +import ethrDidFlowSigned from './shared/ethrDidFlowSigned' import didCommWithPeerDidFlow from './shared/didCommWithPeerDidFlow.js' +import credentialPluginTests from './shared/credentialPluginTests.js' jest.setTimeout(120000) @@ -117,8 +116,7 @@ let dbConnection: Promise let databaseFile: string const setup = async (options?: IAgentOptions): Promise => { - databaseFile = - options?.context?.databaseFile || ':memory:' + databaseFile = options?.context?.databaseFile || ':memory:' dbConnection = new DataSource({ name: options?.context?.['dbName'] || 'test', type: 'sqlite', @@ -202,7 +200,7 @@ const setup = async (options?: IAgentOptions): Promise => { defaultKms: 'local', }), 'did:peer': new PeerDIDProvider({ - defaultKms: 'local' + defaultKms: 'local', }), 'did:pkh': new PkhDIDProvider({ defaultKms: 'local', @@ -303,4 +301,5 @@ describe('Local integration tests', () => { didCommWithPeerDidFlow(testContext) credentialStatus(testContext) ethrDidFlowSigned(testContext) + credentialPluginTests(testContext) }) diff --git a/__tests__/localJsonStoreAgent.test.ts b/__tests__/localJsonStoreAgent.test.ts index 68feaab23..902e0f682 100644 --- a/__tests__/localJsonStoreAgent.test.ts +++ b/__tests__/localJsonStoreAgent.test.ts @@ -76,6 +76,7 @@ import messageHandler from './shared/messageHandler' import utils from './shared/utils' import { JsonFileStore } from './utils/json-file-store' import credentialStatus from './shared/credentialStatus' +import credentialPluginTests from './shared/credentialPluginTests' jest.setTimeout(120000) @@ -241,4 +242,5 @@ describe('Local json-data-store integration tests', () => { didCommPacking(testContext) utils(testContext) credentialStatus(testContext) + credentialPluginTests(testContext) }) diff --git a/__tests__/localMemoryStoreAgent.test.ts b/__tests__/localMemoryStoreAgent.test.ts index 80b9a77e5..54000bab9 100644 --- a/__tests__/localMemoryStoreAgent.test.ts +++ b/__tests__/localMemoryStoreAgent.test.ts @@ -72,6 +72,7 @@ import messageHandler from './shared/messageHandler.js' import utils from './shared/utils.js' import credentialStatus from './shared/credentialStatus.js' import credentialInterop from './shared/credentialInterop.js' +import credentialPluginTests from "./shared/credentialPluginTests.js"; jest.setTimeout(120000) @@ -239,4 +240,5 @@ describe('Local in-memory integration tests', () => { utils(testContext) credentialStatus(testContext) credentialInterop(testContext) + credentialPluginTests(testContext) }) diff --git a/__tests__/restAgent.test.ts b/__tests__/restAgent.test.ts index f5f68dcd5..3d4b88df6 100644 --- a/__tests__/restAgent.test.ts +++ b/__tests__/restAgent.test.ts @@ -99,6 +99,7 @@ import messageHandler from './shared/messageHandler' import didDiscovery from './shared/didDiscovery' import utils from './shared/utils' import credentialStatus from './shared/credentialStatus' +import credentialPluginTests from "./shared/credentialPluginTests"; jest.setTimeout(120000) @@ -303,4 +304,5 @@ describe('REST integration tests', () => { didDiscovery(testContext) utils(testContext) credentialStatus(testContext) + credentialPluginTests(testContext) }) diff --git a/__tests__/shared/credentialPluginTests.ts b/__tests__/shared/credentialPluginTests.ts new file mode 100644 index 000000000..1eae9f8ca --- /dev/null +++ b/__tests__/shared/credentialPluginTests.ts @@ -0,0 +1,88 @@ +// noinspection ES6PreferShortImport + +import { IAgentOptions, ICredentialPlugin, MinimalImportableKey, TAgent } from '../../packages/core-types/src' + +type ConfiguredAgent = TAgent +export default (testContext: { + getAgent: (options?: IAgentOptions) => ConfiguredAgent + setup: (options?: IAgentOptions) => Promise + tearDown: () => Promise +}) => { + describe('credential plugin options', () => { + let agent: ConfiguredAgent + + beforeAll(async () => { + await testContext.setup() + agent = testContext.getAgent() + return true + }) + afterAll(testContext.tearDown) + + it('should list signing options for did:key with Ed25519 key', async () => { + const iid = await agent.didManagerCreate({ + provider: 'did:key', + kms: 'local', + options: { + keyType: 'Ed25519', + }, + }) + + const options = await agent.listUsableProofFormats(iid) + expect(options).toEqual(['jwt', 'lds']) + }) + + it('should list signing options for did:key with Secp256k1 key', async () => { + const iid = await agent.didManagerCreate({ + provider: 'did:key', + kms: 'local', + options: { + keyType: 'Secp256k1', + }, + }) + + const options = await agent.listUsableProofFormats(iid) + expect(options).toEqual(['jwt', 'lds', 'EthereumEip712Signature2021']) + }) + + it('should list signing options for did:key with X25519 key', async () => { + const iid = await agent.didManagerCreate({ + provider: 'did:key', + kms: 'local', + options: { + keyType: 'X25519', + }, + }) + + const options = await agent.listUsableProofFormats(iid) + expect(options).toEqual([]) + }) + + it('should list signing options for did:ethr with web3 backed keys', async () => { + const account = `0x71CB05EE1b1F506fF321Da3dac38f25c0c9ce6E1` + const did = `did:ethr:${account}` + const controllerKeyId = `ethers-${account}` + const iid = await agent.didManagerImport({ + did, + provider: 'did:ethr', + controllerKeyId, + keys: [ + { + kid: controllerKeyId, + type: 'Secp256k1', + kms: 'web3', + privateKeyHex: '', + publicKeyHex: '', + meta: { + account, + provider: 'ethers', + algorithms: ['eth_signMessage', 'eth_signTypedData'], + }, + } as MinimalImportableKey, + ], + }) + + const options = await agent.listUsableProofFormats(iid) + expect(options).toEqual(['EthereumEip712Signature2021']) + }) + }) +} diff --git a/packages/core-types/src/plugin.schema.json b/packages/core-types/src/plugin.schema.json index 586a2a175..eab6c794d 100644 --- a/packages/core-types/src/plugin.schema.json +++ b/packages/core-types/src/plugin.schema.json @@ -4208,7 +4208,7 @@ "save": { "type": "boolean", "description": "If this parameter is true, the resulting VerifiablePresentation is sent to the\n {@link @veramo/core-types#IDataStore | storage plugin } to be saved.", - "deprecated": "Please call\n{@link @veramo/core-types#IDataStore.dataStoreSaveVerifiableCredential | dataStoreSaveVerifiableCredential()} to save\nthe credential after creating it." + "deprecated": "Please call\n{@link @veramo/core-types#IDataStore.dataStoreSaveVerifiableCredential | dataStoreSaveVerifiableCredential()} to\nsave the credential after creating it." }, "proofFormat": { "$ref": "#/components/schemas/ProofFormat", @@ -4422,7 +4422,7 @@ "save": { "type": "boolean", "description": "If this parameter is true, the resulting VerifiablePresentation is sent to the\n {@link @veramo/core-types#IDataStore | storage plugin } to be saved.

", - "deprecated": "Please call\n{@link @veramo/core-types#IDataStore.dataStoreSaveVerifiablePresentation | dataStoreSaveVerifiablePresentation()} to\nsave the credential after creating it." + "deprecated": "Please call\n{@link @veramo/core-types#IDataStore.dataStoreSaveVerifiablePresentation |} * dataStoreSaveVerifiablePresentation()} to save the credential after creating it." }, "challenge": { "type": "string", @@ -4568,6 +4568,167 @@ "proof" ], "description": "Represents a signed Verifiable Presentation (includes proof), using a JSON representation. See {@link https://www.w3.org/TR/vc-data-model/#presentations | VP data model }" + }, + "IIdentifier": { + "type": "object", + "properties": { + "did": { + "type": "string", + "description": "Decentralized identifier" + }, + "alias": { + "type": "string", + "description": "Optional. Identifier alias. Can be used to reference an object in an external system" + }, + "provider": { + "type": "string", + "description": "Identifier provider name" + }, + "controllerKeyId": { + "type": "string", + "description": "Controller key id" + }, + "keys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IKey" + }, + "description": "Array of managed keys" + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IService" + }, + "description": "Array of services" + } + }, + "required": [ + "did", + "provider", + "keys", + "services" + ], + "description": "Identifier interface" + }, + "IKey": { + "type": "object", + "properties": { + "kid": { + "type": "string", + "description": "Key ID" + }, + "kms": { + "type": "string", + "description": "Key Management System" + }, + "type": { + "$ref": "#/components/schemas/TKeyType", + "description": "Key type" + }, + "publicKeyHex": { + "type": "string", + "description": "Public key" + }, + "privateKeyHex": { + "type": "string", + "description": "Optional. Private key" + }, + "meta": { + "anyOf": [ + { + "$ref": "#/components/schemas/KeyMetadata" + }, + { + "type": "null" + } + ], + "description": "Optional. Key metadata. This should be used to determine which algorithms are supported." + } + }, + "required": [ + "kid", + "kms", + "type", + "publicKeyHex" + ], + "description": "Cryptographic key, usually managed by the current Veramo instance." + }, + "TKeyType": { + "type": "string", + "enum": [ + "Ed25519", + "Secp256k1", + "Secp256r1", + "X25519", + "Bls12381G1", + "Bls12381G2" + ], + "description": "Cryptographic key type." + }, + "KeyMetadata": { + "type": "object", + "properties": { + "algorithms": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TAlg" + } + } + }, + "description": "This encapsulates data about a key.\n\nImplementations of {@link @veramo/key-manager#AbstractKeyManagementSystem | AbstractKeyManagementSystem } should populate this object, for each key, with the algorithms that can be performed using it.\n\nThis can also be used to add various tags to the keys under management." + }, + "TAlg": { + "type": "string", + "description": "Known algorithms supported by some of the above key types defined by {@link TKeyType } .\n\nActual implementations of {@link @veramo/key-manager#AbstractKeyManagementSystem | Key Management Systems } can support more. One should check the {@link IKey.meta | IKey.meta.algorithms } property to see what is possible for a particular managed key." + }, + "IService": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID" + }, + "type": { + "type": "string", + "description": "Service type" + }, + "serviceEndpoint": { + "anyOf": [ + { + "$ref": "#/components/schemas/IServiceEndpoint" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/IServiceEndpoint" + } + } + ], + "description": "Endpoint URL" + }, + "description": { + "type": "string", + "description": "Optional. Description" + } + }, + "required": [ + "id", + "type", + "serviceEndpoint" + ], + "description": "Identifier service" + }, + "IServiceEndpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + } + ], + "description": "Represents a service endpoint URL or a map of URLs" } }, "methods": { @@ -4588,6 +4749,18 @@ "returnType": { "$ref": "#/components/schemas/VerifiablePresentation" } + }, + "listUsableProofFormats": { + "description": "Returns a list of supported proof formats.", + "arguments": { + "$ref": "#/components/schemas/IIdentifier" + }, + "returnType": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProofFormat" + } + } } } } diff --git a/packages/core-types/src/types/ICredentialIssuer.ts b/packages/core-types/src/types/ICredentialIssuer.ts index 296800726..9ce6d56e6 100644 --- a/packages/core-types/src/types/ICredentialIssuer.ts +++ b/packages/core-types/src/types/ICredentialIssuer.ts @@ -9,6 +9,7 @@ import { IResolver } from './IResolver.js' import { IDIDManager } from './IDIDManager.js' import { IDataStore } from './IDataStore.js' import { IKeyManager } from './IKeyManager.js' +import { IIdentifier, IKey } from "./IIdentifier.js"; /** * The type of encoding to be used for the Verifiable Credential or Presentation to be generated. @@ -42,8 +43,8 @@ export interface ICreateVerifiablePresentationArgs { * {@link @veramo/core-types#IDataStore | storage plugin} to be saved. *

* @deprecated Please call - * {@link @veramo/core-types#IDataStore.dataStoreSaveVerifiablePresentation | dataStoreSaveVerifiablePresentation()} to - * save the credential after creating it. + * {@link @veramo/core-types#IDataStore.dataStoreSaveVerifiablePresentation | + * dataStoreSaveVerifiablePresentation()} to save the credential after creating it. */ save?: boolean @@ -113,8 +114,8 @@ export interface ICreateVerifiableCredentialArgs { * {@link @veramo/core-types#IDataStore | storage plugin} to be saved. * * @deprecated Please call - * {@link @veramo/core-types#IDataStore.dataStoreSaveVerifiableCredential | dataStoreSaveVerifiableCredential()} to save - * the credential after creating it. + * {@link @veramo/core-types#IDataStore.dataStoreSaveVerifiableCredential | dataStoreSaveVerifiableCredential()} to + * save the credential after creating it. */ save?: boolean @@ -191,8 +192,8 @@ export interface ICredentialIssuer extends IPluginMethodMap { * @param args - Arguments necessary to create the Presentation. * @param context - This reserved param is automatically added and handled by the framework, *do not override* * - * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiableCredential} that was requested or rejects - * with an error if there was a problem with the input or while getting the key to sign + * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiableCredential} that was requested or + * rejects with an error if there was a problem with the input or while getting the key to sign * * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#credentials | Verifiable Credential data model} */ @@ -200,6 +201,24 @@ export interface ICredentialIssuer extends IPluginMethodMap { args: ICreateVerifiableCredentialArgs, context: IssuerAgentContext, ): Promise + + /** + * Returns a list of supported proof formats. + * @param identifier - The identifier that may be used to sign a credential or presentation + * @param context - This reserved param is automatically added and handled by the framework, *do not override* + * + * @beta This API may change without a BREAKING CHANGE notice. + */ + listUsableProofFormats(identifier: IIdentifier, context: IAgentContext<{}>): Promise> + + /** + * Checks if a key is suitable for signing JWT payloads. + * @param key - the key to check for compatibility + * @param context - This reserved param is automatically added and handled by the framework, *do not override* + * + * @internal + */ + matchKeyForJWT(key: IKey, context: IAgentContext): Promise } /** @@ -212,7 +231,7 @@ export interface ICredentialIssuer extends IPluginMethodMap { */ export type IssuerAgentContext = IAgentContext< IResolver & - Pick & - Pick & - Pick + Pick & + Pick & + Pick > diff --git a/packages/core/src/agent.ts b/packages/core/src/agent.ts index 5ed042471..199bdad6d 100644 --- a/packages/core/src/agent.ts +++ b/packages/core/src/agent.ts @@ -29,7 +29,7 @@ const filterUnauthorizedMethods = ( /** * Provides a common context for all plugin methods. * - * This is the main entry point into the API of the DID Agent Framework. + * This is the main entry point into the API of Veramo. * When plugins are installed, they extend the API of the agent and the methods * they provide can all use the common context so that plugins can build on top * of each other and create a richer experience. diff --git a/packages/credential-eip712/src/agent/CredentialEIP712.ts b/packages/credential-eip712/src/agent/CredentialEIP712.ts index 39463faab..086be920b 100644 --- a/packages/credential-eip712/src/agent/CredentialEIP712.ts +++ b/packages/credential-eip712/src/agent/CredentialEIP712.ts @@ -2,6 +2,7 @@ import { CredentialPayload, IAgentPlugin, IIdentifier, + IKey, PresentationPayload, VerifiableCredential, VerifiablePresentation, @@ -10,6 +11,7 @@ import { extractIssuer, getChainIdForDidEthr, getEthereumAddress, + intersect, isDefined, MANDATORY_CREDENTIAL_CONTEXT, mapIdentifierKeysToDoc, @@ -17,7 +19,7 @@ import { removeDIDParameters, resolveDidOrThrow, } from '@veramo/utils' -import schema from "../plugin.schema.json" assert { type: 'json' } +import schema from '../plugin.schema.json' assert { type: 'json' } import { recoverTypedSignature, SignTypedDataVersion } from '@metamask/eth-sig-util' import { @@ -46,6 +48,7 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { createVerifiablePresentationEIP712: this.createVerifiablePresentationEIP712.bind(this), verifyCredentialEIP712: this.verifyCredentialEIP712.bind(this), verifyPresentationEIP712: this.verifyPresentationEIP712.bind(this), + matchKeyForEIP712: this.matchKeyForEIP712.bind(this), } } @@ -146,7 +149,7 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { const compat = { ...eip712Domain, - ...eip712 + ...eip712, } compat.types = compat.types || compat.messageSchema @@ -214,9 +217,10 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { } if (args.presentation.verifiableCredential) { - const credentials = args.presentation.verifiableCredential.map((cred) => { + // EIP712 arrays must use a single data type, so we map all credentials to strings. + presentation.verifiableCredential = args.presentation.verifiableCredential.map((cred) => { // map JWT credentials to their canonical form - if(typeof cred === 'string') { + if (typeof cred === 'string') { return cred } else if (cred.proof.jwt) { return cred.proof.jwt @@ -224,7 +228,6 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { return JSON.stringify(cred) } }) - presentation.verifiableCredential = credentials } const holder = removeDIDParameters(presentation.holder) @@ -303,7 +306,7 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { const compat = { ...eip712Domain, - ...eip712 + ...eip712, } compat.types = compat.types || compat.messageSchema @@ -343,4 +346,16 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { return false } + + /** + * Checks if a key is suitable for signing EIP712 payloads. + * This relies on the metadata set by the key management system to determine if this key can sign EIP712 payloads. + * + * @param k - the key to check + * + * @internal + */ + async matchKeyForEIP712(k: IKey): Promise { + return intersect(k.meta?.algorithms ?? [], ['eth_signTypedData', 'EthereumEip712Signature2021']).length > 0 + } } diff --git a/packages/credential-eip712/src/types/ICredentialEIP712.ts b/packages/credential-eip712/src/types/ICredentialEIP712.ts index 1a83d477b..9a5d4d5b4 100644 --- a/packages/credential-eip712/src/types/ICredentialEIP712.ts +++ b/packages/credential-eip712/src/types/ICredentialEIP712.ts @@ -2,6 +2,7 @@ import { CredentialPayload, IAgentContext, IDIDManager, + IKey, IKeyManager, IPluginMethodMap, IResolver, @@ -15,7 +16,8 @@ import { * that use EIP712 proof format. * * @remarks Please see {@link https://www.w3.org/TR/vc-data-model | W3C Verifiable Credentials data model} - * @remarks Please see {@link https://w3c-ccg.github.io/ethereum-eip712-signature-2021-spec/ | EthereumEip712Signature2021} + * @remarks Please see + * {@link https://w3c-ccg.github.io/ethereum-eip712-signature-2021-spec/ | EthereumEip712Signature2021} * * @beta This API may change without a BREAKING CHANGE notice. */ @@ -27,8 +29,8 @@ export interface ICredentialIssuerEIP712 extends IPluginMethodMap { * @param args - Arguments necessary to create the Credential. * @param context - This reserved param is automatically added and handled by the framework, *do not override* * - * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiableCredential} that was requested or rejects with an error - * if there was a problem with the input or while getting the key to sign + * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiableCredential} that was requested or + * rejects with an error if there was a problem with the input or while getting the key to sign * * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#credentials | Verifiable Credential data model} * @@ -60,10 +62,11 @@ export interface ICredentialIssuerEIP712 extends IPluginMethodMap { * @param args - Arguments necessary to create the Presentation. * @param context - This reserved param is automatically added and handled by the framework, *do not override* * - * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiablePresentation} that was requested or rejects with an error - * if there was a problem with the input or while getting the key to sign + * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiablePresentation} that was requested or + * rejects with an error if there was a problem with the input or while getting the key to sign * - * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#presentations | Verifiable Presentation data model } + * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#presentations | Verifiable Presentation data model + * } */ createVerifiablePresentationEIP712( args: ICreateVerifiablePresentationEIP712Args, @@ -81,6 +84,17 @@ export interface ICredentialIssuerEIP712 extends IPluginMethodMap { * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#presentations | Verifiable Credential data model} */ verifyPresentationEIP712(args: IVerifyPresentationEIP712Args, context: IRequiredContext): Promise + + /** + * Checks if a key is suitable for signing EIP712 payloads. + * This relies on the metadata set by the key management system to determine if this key can sign EIP712 payloads. + * + * @param key - the key to check + * @param context - This reserved param is automatically added and handled by the framework, *do not override* + * + * @internal + */ + matchKeyForEIP712(key: IKey, context: IRequiredContext): Promise } /** diff --git a/packages/credential-ld/src/action-handler.ts b/packages/credential-ld/src/action-handler.ts index ee1b55bba..f7b788576 100644 --- a/packages/credential-ld/src/action-handler.ts +++ b/packages/credential-ld/src/action-handler.ts @@ -16,6 +16,7 @@ import { LdContextLoader } from './ld-context-loader.js' import { _ExtendedIKey, extractIssuer, + intersect, isDefined, MANDATORY_CREDENTIAL_CONTEXT, mapIdentifierKeysToDoc, @@ -61,6 +62,7 @@ export class CredentialIssuerLD implements IAgentPlugin { createVerifiableCredentialLD: this.createVerifiableCredentialLD.bind(this), verifyCredentialLD: this.verifyCredentialLD.bind(this), verifyPresentationLD: this.verifyPresentationLD.bind(this), + matchKeyForLDSuite: this.matchKeyForLDSuite.bind(this), } } @@ -255,9 +257,32 @@ export class CredentialIssuerLD implements IAgentPlugin { signingKey = extendedKeys.find((k) => supportedTypes.includes(k.meta.verificationMethod.type)) } - if (!signingKey) throw Error(`key_not_found: No suitable signing key found for ${identifier.did}`) verificationMethodId = signingKey.meta.verificationMethod.id return { signingKey, verificationMethodId } } + + /** + * Returns true if the key is supported by any of the installed LD Signature suites + * @param k - the key to verify + * + * @internal + */ + async matchKeyForLDSuite(k: IKey): Promise { + // prefilter based on key algorithms + switch (k.type) { + case 'Ed25519': + if (!k.meta?.algorithms?.includes('EdDSA')) return false + break + case 'Secp256k1': + if (intersect(k.meta?.algorithms ?? [], ['ES256K-R', 'ES256K']).length == 0) return false + break + } + + // TODO: this should return a list of supported suites, not just a boolean + const suites = this.ldCredentialModule.ldSuiteLoader.getAllSignatureSuites() + return suites + .map((suite: VeramoLdSignature) => suite.getSupportedVeramoKeyType().includes(k.type)) + .some((supportsThisKey: boolean) => supportsThisKey) + } } diff --git a/packages/credential-ld/src/types.ts b/packages/credential-ld/src/types.ts index d8c91f46e..4e8ac2744 100644 --- a/packages/credential-ld/src/types.ts +++ b/packages/credential-ld/src/types.ts @@ -2,6 +2,7 @@ import { CredentialPayload, IAgentContext, IDIDManager, + IKey, IKeyManager, IPluginMethodMap, IResolver, @@ -27,10 +28,11 @@ export interface ICredentialIssuerLD extends IPluginMethodMap { * @param args - Arguments necessary to create the Presentation. * @param context - This reserved param is automatically added and handled by the framework, *do not override* * - * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiablePresentation} that was requested or rejects with an error - * if there was a problem with the input or while getting the key to sign + * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiablePresentation} that was requested or + * rejects with an error if there was a problem with the input or while getting the key to sign * - * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#presentations | Verifiable Presentation data model } + * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#presentations | Verifiable Presentation data model + * } * * @beta This API may change without a BREAKING CHANGE notice. */ @@ -46,8 +48,8 @@ export interface ICredentialIssuerLD extends IPluginMethodMap { * @param args - Arguments necessary to create the Presentation. * @param context - This reserved param is automatically added and handled by the framework, *do not override* * - * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiableCredential} that was requested or rejects with an error - * if there was a problem with the input or while getting the key to sign + * @returns - a promise that resolves to the {@link @veramo/core-types#VerifiableCredential} that was requested or + * rejects with an error if there was a problem with the input or while getting the key to sign * * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#credentials | Verifiable Credential data model} * @@ -85,6 +87,15 @@ export interface ICredentialIssuerLD extends IPluginMethodMap { * @beta This API may change without a BREAKING CHANGE notice. */ verifyPresentationLD(args: IVerifyPresentationLDArgs, context: IRequiredContext): Promise + + /** + * Returns true if the key is supported by any of the installed LD Signature suites + * @param key - the key to verify + * @param context - This reserved param is automatically added and handled by the framework, *do not override* + * + * @internal + */ + matchKeyForLDSuite(key: IKey, context: IAgentContext<{}>): Promise } /** diff --git a/packages/credential-w3c/src/__tests__/message-handler.test.ts b/packages/credential-w3c/src/__tests__/message-handler.test.ts index 3033afe44..d16e65ecb 100644 --- a/packages/credential-w3c/src/__tests__/message-handler.test.ts +++ b/packages/credential-w3c/src/__tests__/message-handler.test.ts @@ -1,4 +1,4 @@ -import { DIDResolutionResult, IAgentContext, ICredentialPlugin, IResolver } from '../../../core-types/src' +import { DIDResolutionResult, IAgentContext, ICredentialVerifier, IResolver } from '../../../core-types/src' import { Message } from '../../../message-handler/src' import { IContext, MessageTypes, W3cMessageHandler } from '../message-handler.js' import { jest } from '@jest/globals' @@ -64,13 +64,11 @@ describe('@veramo/credential-w3c', () => { } } }, - createVerifiableCredential: jest.fn(), - createVerifiablePresentation: jest.fn(), verifyCredential: jest.fn(), verifyPresentation: jest.fn(), getDIDComponentById: jest.fn(), }, - } as IAgentContext + } as IAgentContext it('should reject unknown message type', async () => { expect.assertions(1) diff --git a/packages/credential-w3c/src/action-handler.ts b/packages/credential-w3c/src/action-handler.ts index e89a96fc5..dd453585c 100644 --- a/packages/credential-w3c/src/action-handler.ts +++ b/packages/credential-w3c/src/action-handler.ts @@ -12,6 +12,7 @@ import { IVerifyCredentialArgs, IVerifyPresentationArgs, IVerifyResult, + ProofFormat, VerifiableCredential, VerifiablePresentation, VerifierAgentContext, @@ -39,6 +40,7 @@ import { isDefined, MANDATORY_CREDENTIAL_CONTEXT, processEntryToArray, + intersect, } from '@veramo/utils' import Debug from 'debug' import { Resolvable } from 'did-resolver' @@ -79,6 +81,8 @@ export class CredentialPlugin implements IAgentPlugin { createVerifiableCredential: this.createVerifiableCredential.bind(this), verifyCredential: this.verifyCredential.bind(this), verifyPresentation: this.verifyPresentation.bind(this), + matchKeyForJWT: this.matchKeyForJWT.bind(this), + listUsableProofFormats: this.listUsableProofFormats.bind(this), } } @@ -477,13 +481,58 @@ export class CredentialPlugin implements IAgentPlugin { } } } + + /** + * Checks if a key is suitable for signing JWT payloads. + * @param key + * @param context + * + * @internal + */ + async matchKeyForJWT(key: IKey, context: IssuerAgentContext): Promise { + switch (key.type) { + case 'Ed25519': + case 'Secp256r1': + return true + case 'Secp256k1': + return intersect(key.meta?.algorithms ?? [], ['ES256K', 'ES256K-R']).length > 0 + default: + return false + } + return false + } + + async listUsableProofFormats(did: IIdentifier, context: IssuerAgentContext): Promise { + const signingOptions: ProofFormat[] = [] + const keys = did.keys + for (const key of keys) { + if (context.agent.availableMethods().includes('matchKeyForJWT')) { + if (await context.agent.matchKeyForJWT(key)) { + signingOptions.push('jwt') + } + } + if (context.agent.availableMethods().includes('matchKeyForLDSuite')) { + if (await context.agent.matchKeyForLDSuite(key)) { + signingOptions.push('lds') + } + } + if (context.agent.availableMethods().includes('matchKeyForEIP712')) { + if (await context.agent.matchKeyForEIP712(key)) { + signingOptions.push('EthereumEip712Signature2021') + } + } + } + return signingOptions + } } function pickSigningKey(identifier: IIdentifier, keyRef?: string): IKey { let key: IKey | undefined if (!keyRef) { - key = identifier.keys.find((k) => k.type === 'Secp256k1' || k.type === 'Ed25519' || k.type === 'Secp256r1') + key = identifier.keys.find( + (k) => k.type === 'Secp256k1' || k.type === 'Ed25519' || k.type === 'Secp256r1', + ) if (!key) throw Error('key_not_found: No signing key for ' + identifier.did) } else { key = identifier.keys.find((k) => k.kid === keyRef)