diff --git a/packages/cli/src/explore/utils.ts b/packages/cli/src/explore/utils.ts index bd7ead71a..0f8246dc6 100644 --- a/packages/cli/src/explore/utils.ts +++ b/packages/cli/src/explore/utils.ts @@ -3,6 +3,7 @@ import os from 'os' export function shortDid(did?: string): string { if (!did) return '' + did = did.replace(/\?.*$/, '') if (did.slice(0, 7) === 'did:web') { return did } diff --git a/packages/credential-eip712/src/agent/CredentialEIP712.ts b/packages/credential-eip712/src/agent/CredentialEIP712.ts index 067f85e5a..39463faab 100644 --- a/packages/credential-eip712/src/agent/CredentialEIP712.ts +++ b/packages/credential-eip712/src/agent/CredentialEIP712.ts @@ -14,6 +14,7 @@ import { MANDATORY_CREDENTIAL_CONTEXT, mapIdentifierKeysToDoc, processEntryToArray, + removeDIDParameters, resolveDidOrThrow, } from '@veramo/utils' import schema from "../plugin.schema.json" assert { type: 'json' } @@ -63,7 +64,7 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { issuanceDate = issuanceDate.toISOString() } - const issuer = extractIssuer(args.credential) + const issuer = extractIssuer(args.credential, { removeParameters: true }) if (!issuer || typeof issuer === 'undefined') { throw new Error('invalid_argument: credential.issuer must not be empty') } @@ -226,9 +227,11 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { presentation.verifiableCredential = credentials } + const holder = removeDIDParameters(presentation.holder) + let identifier: IIdentifier try { - identifier = await context.agent.didManagerGet({ did: presentation.holder }) + identifier = await context.agent.didManagerGet({ did: holder }) } catch (e) { throw new Error('invalid_argument: presentation.holder must be a DID managed by this agent') } diff --git a/packages/credential-ld/src/action-handler.ts b/packages/credential-ld/src/action-handler.ts index 3b240afc9..ee1b55bba 100644 --- a/packages/credential-ld/src/action-handler.ts +++ b/packages/credential-ld/src/action-handler.ts @@ -22,6 +22,7 @@ import { OrPromise, processEntryToArray, RecordLike, + removeDIDParameters, } from '@veramo/utils' import { LdCredentialModule } from './ld-credential-module.js' @@ -98,9 +99,11 @@ export class CredentialIssuerLD implements IAgentPlugin { //issuanceDate must not be present for presentations because it is not defined in a @context delete presentation.issuanceDate + const holder = removeDIDParameters(presentation.holder) + let identifier: IIdentifier try { - identifier = await context.agent.didManagerGet({ did: presentation.holder }) + identifier = await context.agent.didManagerGet({ did: holder }) } catch (e) { throw new Error('invalid_argument: args.presentation.holder must be a DID managed by this agent') } @@ -148,7 +151,7 @@ export class CredentialIssuerLD implements IAgentPlugin { type: credentialType, } - const issuer = extractIssuer(credential) + const issuer = extractIssuer(credential, { removeParameters: true }) if (!issuer || typeof issuer === 'undefined') { throw new Error('invalid_argument: args.credential.issuer must not be empty') } @@ -252,7 +255,7 @@ 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 } diff --git a/packages/credential-w3c/src/__tests__/action-handler.test.ts b/packages/credential-w3c/src/__tests__/action-handler.test.ts index dde7a6e64..9a0f03fdf 100644 --- a/packages/credential-w3c/src/__tests__/action-handler.test.ts +++ b/packages/credential-w3c/src/__tests__/action-handler.test.ts @@ -73,6 +73,20 @@ const mockIdentifiers: IIdentifier[] = [ ], services: [], }, + { + did: 'did:example:444?versionTime=2023-01-01T00:00:00Z', + provider: 'mock', + controllerKeyId: 'kid4', + keys: [ + { + kid: 'kid4', + publicKeyHex: 'pub', + type: 'Ed25519', + kms: 'mock', + }, + ], + services: [], + }, ] const w3c = new CredentialPlugin() @@ -114,6 +128,9 @@ describe('@veramo/credential-w3c', () => { test.each(mockIdentifiers)('handles createVerifiableCredential', async (mockIdentifier) => { expect.assertions(6) + const issuerId = mockIdentifier.did; + mockIdentifier.did = mockIdentifier.did.replace(/\?.*$/, '') + const keyRef = mockIdentifier.keys[1]?.kid // Second key or undefined const expectedKey = mockIdentifier.keys[mockIdentifier.keys.length - 1] @@ -123,7 +140,7 @@ describe('@veramo/credential-w3c', () => { const credential: CredentialPayload = { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2020/demo/4342323'], type: ['VerifiableCredential', 'PublicProfile'], - issuer: { id: mockIdentifier.did }, + issuer: { id: issuerId }, issuanceDate: new Date().toISOString(), id: 'vc1', credentialSubject: { @@ -157,7 +174,10 @@ describe('@veramo/credential-w3c', () => { }) test.each(mockIdentifiers)('handles createVerifiablePresentation', async (mockIdentifier) => { - expect.assertions(3) + expect.assertions(4) + + const issuerId = mockIdentifier.did; + mockIdentifier.did = mockIdentifier.did.replace(/\?.*$/, '') agent.didManagerGet = jest.fn(async (args): Promise => mockIdentifier) const context = { agent } @@ -167,7 +187,7 @@ describe('@veramo/credential-w3c', () => { credential: { '@context': ['https://www.w3.org/2018/credentials/v1'], type: ['VerifiableCredential', 'PublicProfile'], - issuer: { id: mockIdentifier.did }, + issuer: { id: issuerId }, issuanceDate: new Date().toISOString(), id: 'vc1', credentialSubject: { @@ -189,7 +209,7 @@ describe('@veramo/credential-w3c', () => { const presentation: PresentationPayload = { '@context': ['https://www.w3.org/2018/credentials/v1'], type: ['VerifiablePresentation'], - holder: mockIdentifier.did, + holder: mockIdentifier.did + '?versionTime=2023-01-01T00:00:00Z', issuanceDate: new Date().toISOString(), verifiableCredential: [credential], } @@ -204,6 +224,7 @@ describe('@veramo/credential-w3c', () => { ) expect(context.agent.didManagerGet).toBeCalledWith({ did: mockIdentifier.did }) + expect(context.agent.didManagerGet).not.toBeCalledWith({ did: presentation.holder }) expect(context.agent.dataStoreSaveVerifiablePresentation).not.toBeCalled() expect(vp.holder).toEqual(mockIdentifier.did) }) diff --git a/packages/credential-w3c/src/action-handler.ts b/packages/credential-w3c/src/action-handler.ts index ec5a7f2de..e89a96fc5 100644 --- a/packages/credential-w3c/src/action-handler.ts +++ b/packages/credential-w3c/src/action-handler.ts @@ -35,6 +35,7 @@ import { decodeJWT } from 'did-jwt' import { asArray, extractIssuer, + removeDIDParameters, isDefined, MANDATORY_CREDENTIAL_CONTEXT, processEntryToArray, @@ -123,9 +124,11 @@ export class CredentialPlugin implements IAgentPlugin { }) } + const holder = removeDIDParameters(presentation.holder) + let identifier: IIdentifier try { - identifier = await context.agent.didManagerGet({ did: presentation.holder }) + identifier = await context.agent.didManagerGet({ did: holder }) } catch (e) { throw new Error('invalid_argument: presentation.holder must be a DID managed by this agent') } @@ -205,7 +208,7 @@ export class CredentialPlugin implements IAgentPlugin { } //FIXME: if the identifier is not found, the error message should reflect that. - const issuer = extractIssuer(credential) + const issuer = extractIssuer(credential, { removeParameters: true }) if (!issuer || typeof issuer === 'undefined') { throw new Error('invalid_argument: credential.issuer must not be empty') } diff --git a/packages/selective-disclosure/src/action-handler.ts b/packages/selective-disclosure/src/action-handler.ts index f45de0e3c..ace18080f 100644 --- a/packages/selective-disclosure/src/action-handler.ts +++ b/packages/selective-disclosure/src/action-handler.ts @@ -202,7 +202,7 @@ export class SelectiveDisclosure implements IAgentPlugin { if ( credentialRequest.issuers && - !credentialRequest.issuers.map((i) => i.did).includes(extractIssuer(credential)) + !credentialRequest.issuers.map((i) => i.did).includes(extractIssuer(credential, { removeParameters: true })) ) { return false } diff --git a/packages/utils/src/__tests__/credential-utils.test.ts b/packages/utils/src/__tests__/credential-utils.test.ts index a2499bce2..b7b34c503 100644 --- a/packages/utils/src/__tests__/credential-utils.test.ts +++ b/packages/utils/src/__tests__/credential-utils.test.ts @@ -225,6 +225,12 @@ describe('@veramo/utils credential utils', () => { type: 'fake', }, } + const ldCred3 = { + issuer: 'did:key:z6MkvGFkoFarw7pXRBkKqZKwDcc2L3U4AZC1RtBiceicUHqn?versionTime=2023-01-01T00:00:00Z', + proof: { + type: 'fake', + }, + } const ldPres = { holder: 'did:key:z6MkvGFkoFarw7pXRBkKqZKwDcc2L3U4AZC1RtBiceicUHqn', proof: { @@ -235,6 +241,8 @@ describe('@veramo/utils credential utils', () => { 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7InlvdSI6IlJvY2sifX0sInN1YiI6ImRpZDp3ZWI6ZXhhbXBsZS5jb20iLCJuYmYiOjE2Mzc2ODAzMTcsImlzcyI6ImRpZDpldGhyOnJpbmtlYnk6MHgwMmY2NDk1NWRkMDVkZGU1NTkxMGYzNzllYzU3ODVlZWUwOGJiZTZlNTBkZGFlNmJhY2UwNTk2ZmQ4ZDQ2MDE4ZjcifQ.ujM4zNm8h5-Cg01vh4ka_7NmdwYl8HAjO90XjxYYwSa0-4rqzM5ndt-OE6vS6y0gPwhwlQWHmDxg4X7OTzCUoQ' expect(extractIssuer(ldCred1)).toEqual('did:key:z6MkvGFkoFarw7pXRBkKqZKwDcc2L3U4AZC1RtBiceicUHqn') expect(extractIssuer(ldCred2)).toEqual('did:key:z6MkvGFkoFarw7pXRBkKqZKwDcc2L3U4AZC1RtBiceicUHqn') + expect(extractIssuer(ldCred3)).toEqual('did:key:z6MkvGFkoFarw7pXRBkKqZKwDcc2L3U4AZC1RtBiceicUHqn?versionTime=2023-01-01T00:00:00Z') + expect(extractIssuer(ldCred3, { removeParameters: true })).toEqual('did:key:z6MkvGFkoFarw7pXRBkKqZKwDcc2L3U4AZC1RtBiceicUHqn') expect(extractIssuer(ldPres)).toEqual('did:key:z6MkvGFkoFarw7pXRBkKqZKwDcc2L3U4AZC1RtBiceicUHqn') expect(extractIssuer(jwt)).toEqual( 'did:ethr:rinkeby:0x02f64955dd05dde55910f379ec5785eee08bbe6e50ddae6bace0596fd8d46018f7', diff --git a/packages/utils/src/credential-utils.ts b/packages/utils/src/credential-utils.ts index ea5c59e1d..a14a0ba74 100644 --- a/packages/utils/src/credential-utils.ts +++ b/packages/utils/src/credential-utils.ts @@ -104,6 +104,8 @@ export function computeEntryHash( * `iss` from a JWT or `issuer`/`issuer.id` from a VC or `holder` from a VP * * @param input - the credential or presentation whose issuer/holder needs to be extracted. + * @param options + * removeParameters - Remove all DID parameters from the issuer ID * * @beta This API may change without a BREAKING CHANGE notice. */ @@ -114,6 +116,7 @@ export function extractIssuer( | CredentialPayload | PresentationPayload | null, + options: { removeParameters?: boolean } = {} ): string { if (!isDefined(input)) { return '' @@ -121,7 +124,8 @@ export function extractIssuer( // JWT try { const { payload } = decodeJWT(input) - return payload.iss || '' + const iss = payload.iss || '' + return !!options.removeParameters ? removeDIDParameters(iss) : iss } catch (e: any) { return '' } @@ -135,6 +139,18 @@ export function extractIssuer( } else { iss = '' } - return typeof iss === 'string' ? iss : iss?.id || '' + if (typeof iss !== 'string') iss = iss.id || '' + return !!options.removeParameters ? removeDIDParameters(iss) : iss } } + +/** + * Remove all DID parameters from a DID url + * + * @param did - the DID URL + * + * @beta This API may change without a BREAKING CHANGE notice. + */ +export function removeDIDParameters(did: string): string { + return did.replace(/\?.+$/, '') +}