Skip to content

Commit

Permalink
feat(credential-w3c): allow issuers with query parameters for credent…
Browse files Browse the repository at this point in the history
…ials and presentations (#1207)

fixes #1201
  • Loading branch information
jasny authored Aug 1, 2023
1 parent 1f84ae7 commit 688f59d
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/cli/src/explore/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
7 changes: 5 additions & 2 deletions packages/credential-eip712/src/agent/CredentialEIP712.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
MANDATORY_CREDENTIAL_CONTEXT,
mapIdentifierKeysToDoc,
processEntryToArray,
removeDIDParameters,
resolveDidOrThrow,
} from '@veramo/utils'
import schema from "../plugin.schema.json" assert { type: 'json' }
Expand Down Expand Up @@ -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')
}
Expand Down Expand Up @@ -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')
}
Expand Down
9 changes: 6 additions & 3 deletions packages/credential-ld/src/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
OrPromise,
processEntryToArray,
RecordLike,
removeDIDParameters,
} from '@veramo/utils'

import { LdCredentialModule } from './ld-credential-module.js'
Expand Down Expand Up @@ -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')
}
Expand Down Expand Up @@ -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')
}
Expand Down Expand Up @@ -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 }
Expand Down
29 changes: 25 additions & 4 deletions packages/credential-w3c/src/__tests__/action-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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]

Expand All @@ -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: {
Expand Down Expand Up @@ -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<IIdentifier> => mockIdentifier)
const context = { agent }
Expand All @@ -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: {
Expand All @@ -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],
}
Expand All @@ -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)
})
Expand Down
7 changes: 5 additions & 2 deletions packages/credential-w3c/src/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { decodeJWT } from 'did-jwt'
import {
asArray,
extractIssuer,
removeDIDParameters,
isDefined,
MANDATORY_CREDENTIAL_CONTEXT,
processEntryToArray,
Expand Down Expand Up @@ -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')
}
Expand Down Expand Up @@ -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')
}
Expand Down
2 changes: 1 addition & 1 deletion packages/selective-disclosure/src/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
8 changes: 8 additions & 0 deletions packages/utils/src/__tests__/credential-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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',
Expand Down
20 changes: 18 additions & 2 deletions packages/utils/src/credential-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -114,14 +116,16 @@ export function extractIssuer(
| CredentialPayload
| PresentationPayload
| null,
options: { removeParameters?: boolean } = {}
): string {
if (!isDefined(input)) {
return ''
} else if (typeof input === 'string') {
// JWT
try {
const { payload } = decodeJWT(input)
return payload.iss || ''
const iss = payload.iss || ''
return !!options.removeParameters ? removeDIDParameters(iss) : iss
} catch (e: any) {
return ''
}
Expand All @@ -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(/\?.+$/, '')
}

0 comments on commit 688f59d

Please sign in to comment.