@sphereon/ssi-sdk.credential-vcdm
Version:
Plugin for working with W3C Verifiable Credentials DataModel 1 and 2 Credentials & Presentations.
161 lines (133 loc) • 5.19 kB
text/typescript
import type { IAgentContext, ICredentialVerifier, IResolver, VerifiableCredential, VerifiablePresentation } from '@veramo/core'
import { AbstractMessageHandler, Message } from '@veramo/message-handler'
import { asArray, computeEntryHash, decodeCredentialToObject, extractIssuer } from '@veramo/utils'
import {
normalizeCredential,
normalizePresentation,
validateJwtCredentialPayload,
validateJwtPresentationPayload, // @ts-ignore
} from 'did-jwt-vc'
import { v4 as uuidv4 } from 'uuid'
import Debug from 'debug'
const debug = Debug('sphereon:vcdm:message-handler')
/**
* These types are used by `@veramo/data-store` when storing Verifiable Credentials and Presentations
*
* @internal
*/
export const MessageTypes = {
/** Represents a Verifiable Credential */
vc: 'w3c.vc',
/** Represents a Verifiable Presentation */
vp: 'w3c.vp',
}
/**
* Represents the requirements that this plugin has.
* The agent that is using this plugin is expected to provide these methods.
*
* This interface can be used for static type checks, to make sure your application is properly initialized.
*/
export type IContext = IAgentContext<IResolver & ICredentialVerifier>
/**
* An implementation of the {@link @veramo/message-handler#AbstractMessageHandler}.
*
* This plugin can handle incoming W3C Verifiable Credentials and Presentations and prepare them
* for internal storage as {@link @veramo/message-handler#Message} types.
*
* The current version can only handle `JWT` encoded
*
* @remarks {@link @veramo/core#IDataStore | IDataStore }
*
* @public
*/
export class W3cMessageHandler extends AbstractMessageHandler {
async handle(message: Message, context: IContext): Promise<Message> {
const meta = message.getLastMetaData()
// console.log(JSON.stringify(message, null, 2))
//FIXME: messages should not be expected to be only JWT
if (meta?.type === 'JWT' && message.raw) {
const { data } = message
try {
validateJwtPresentationPayload(data)
//FIXME: flagging this for potential privacy leaks
debug('JWT is', MessageTypes.vp)
const presentation = normalizePresentation(message.raw)
const credentials = presentation.verifiableCredential
message.id = computeEntryHash(message.raw)
message.type = MessageTypes.vp
message.from = presentation.holder
message.to = presentation.verifier?.[0]
if (presentation.tag) {
message.threadId = presentation.tag
}
message.createdAt = presentation.issuanceDate
message.presentations = [presentation]
message.credentials = credentials
return message
} catch (e) {}
try {
validateJwtCredentialPayload(data)
//FIXME: flagging this for potential privacy leaks
debug('JWT is', MessageTypes.vc)
const credential = normalizeCredential(message.raw)
message.id = computeEntryHash(message.raw)
message.type = MessageTypes.vc
message.from = credential.issuer.id
message.to = credential.credentialSubject.id
if (credential.tag) {
message.threadId = credential.tag
}
message.createdAt = credential.issuanceDate
message.credentials = [credential]
return message
} catch (e) {}
}
// LDS Verification and Handling
if (message.type === MessageTypes.vc && message.data) {
// verify credential
const credential = message.data as VerifiableCredential
const result = await context.agent.verifyCredential({ credential })
if (result.verified) {
message.id = computeEntryHash(message.raw || message.id || uuidv4())
message.type = MessageTypes.vc
message.from = extractIssuer(credential)
message.to = credential.credentialSubject.id
if (credential.tag) {
message.threadId = credential.tag
}
message.createdAt = credential.issuanceDate
message.credentials = [credential]
return message
} else {
throw new Error(result.error?.message)
}
}
if (message.type === MessageTypes.vp && message.data) {
// verify presentation
const presentation = message.data as VerifiablePresentation
// throws on error.
const result = await context.agent.verifyPresentation({
presentation,
// FIXME: HARDCODED CHALLENGE VERIFICATION FOR NOW
challenge: 'VERAMO',
domain: 'VERAMO',
})
if (result.verified) {
message.id = computeEntryHash(message.raw || message.id || uuidv4())
message.type = MessageTypes.vp
message.from = presentation.holder
// message.to = presentation.verifier?.[0]
if (presentation.tag) {
message.threadId = presentation.tag
}
// message.createdAt = presentation.issuanceDate
message.presentations = [presentation]
message.credentials = asArray(presentation.verifiableCredential).map(decodeCredentialToObject)
return message
} else {
throw new Error(result.error?.message)
}
}
return super.handle(message, context)
}
}