UNPKG

@sphereon/ssi-sdk.credential-vcdm

Version:

Plugin for working with W3C Verifiable Credentials DataModel 1 and 2 Credentials & Presentations.

458 lines (453 loc) 16.2 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/message-handler.ts import { AbstractMessageHandler } from "@veramo/message-handler"; import { asArray, computeEntryHash, decodeCredentialToObject, extractIssuer } from "@veramo/utils"; import { normalizeCredential, normalizePresentation, validateJwtCredentialPayload, validateJwtPresentationPayload } from "did-jwt-vc"; import { v4 as uuidv4 } from "uuid"; import Debug from "debug"; var debug = Debug("sphereon:vcdm:message-handler"); var MessageTypes = { /** Represents a Verifiable Credential */ vc: "w3c.vc", /** Represents a Verifiable Presentation */ vp: "w3c.vp" }; var W3cMessageHandler = class extends AbstractMessageHandler { static { __name(this, "W3cMessageHandler"); } async handle(message, context) { const meta = message.getLastMetaData(); if (meta?.type === "JWT" && message.raw) { const { data } = message; try { validateJwtPresentationPayload(data); 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); 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) { } } if (message.type === MessageTypes.vc && message.data) { const credential = message.data; 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) { const presentation = message.data; 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; if (presentation.tag) { message.threadId = presentation.tag; } message.presentations = [ presentation ]; message.credentials = asArray(presentation.verifiableCredential).map(decodeCredentialToObject); return message; } else { throw new Error(result.error?.message); } } return super.handle(message, context); } }; // src/vcdmCredentialPlugin.ts import { asArray as asArray2 } from "@sphereon/ssi-sdk.core"; import { CredentialMapper } from "@sphereon/ssi-types"; import { schema } from "@veramo/core"; import Debug2 from "debug"; // src/functions.ts import { isDefined, processEntryToArray } from "@veramo/utils"; import { decodeJWT } from "did-jwt"; import { addVcdmContextIfNeeded, isVcdm1Credential, isVcdm2Credential, VCDM_CREDENTIAL_CONTEXT_V1, VCDM_CREDENTIAL_CONTEXT_V2 } from "@sphereon/ssi-types"; import { getKey } from "@sphereon/ssi-sdk-ext.did-utils"; function extractIssuer2(input, options = {}) { if (!isDefined(input)) { return ""; } else if (typeof input === "string") { try { const { payload } = decodeJWT(input.split(`~`)[0]); const iss = payload.iss ?? ""; return !!options.removeParameters ? removeDIDParameters(iss) : iss; } catch (e) { return ""; } } else { let iss; if (input.issuer) { iss = input.issuer; } else if (input.holder) { iss = input.holder; } else { iss = ""; } if (typeof iss !== "string") iss = iss.id ?? ""; return !!options.removeParameters ? removeDIDParameters(iss) : iss; } } __name(extractIssuer2, "extractIssuer"); function removeDIDParameters(did) { return did.replace(/\?.*$/, ""); } __name(removeDIDParameters, "removeDIDParameters"); async function pickSigningKey({ identifier, kmsKeyRef }, context) { const key = await getKey({ identifier, vmRelationship: "assertionMethod", kmsKeyRef }, context); return key; } __name(pickSigningKey, "pickSigningKey"); async function isRevoked(credential, context) { if (!credential.credentialStatus) return false; if (typeof context.agent.checkCredentialStatus === "function") { const status = await context.agent.checkCredentialStatus({ credential }); return status?.revoked == true || status?.verified === false; } throw new Error(`invalid_setup: The credential status can't be verified because there is no ICredentialStatusVerifier plugin installed.`); } __name(isRevoked, "isRevoked"); function preProcessCredentialPayload({ credential, now = /* @__PURE__ */ new Date() }) { const credentialContext = addVcdmContextIfNeeded(credential?.["@context"]); const isVdcm1 = isVcdm1Credential(credential); const isVdcm2 = isVcdm2Credential(credential); const credentialType = processEntryToArray(credential?.type, "VerifiableCredential"); let issuanceDate = credential?.validFrom ?? credential?.issuanceDate ?? (typeof now === "number" ? new Date(now) : now).toISOString(); let expirationDate = credential?.validUntil ?? credential?.expirationDate; if (issuanceDate instanceof Date) { issuanceDate = issuanceDate.toISOString(); } const credentialPayload = { ...credential, "@context": credentialContext, type: credentialType, ...isVdcm1 && { issuanceDate }, ...isVdcm1 && expirationDate && { expirationDate }, ...isVdcm2 && { validFrom: issuanceDate }, ...isVdcm2 && expirationDate && { validUntil: expirationDate } }; if (isVdcm1) { delete credentialPayload.validFrom; delete credentialPayload.validUntil; } else if (isVdcm2) { delete credentialPayload.issuanceDate; delete credentialPayload.expirationDate; } const issuer = extractIssuer2(credentialPayload, { removeParameters: true }); if (!issuer || typeof issuer === "undefined") { throw new Error("invalid_argument: args.credential.issuer must not be empty"); } return { credential: credentialPayload, issuer, now }; } __name(preProcessCredentialPayload, "preProcessCredentialPayload"); function preProcessPresentation(args) { const { presentation, now = /* @__PURE__ */ new Date() } = args; const credentials = presentation?.verifiableCredential ?? []; const v1Credential = credentials.find((cred) => typeof cred === "object" && cred["@context"].includes(VCDM_CREDENTIAL_CONTEXT_V1)) ? VCDM_CREDENTIAL_CONTEXT_V1 : void 0; const v2Credential = credentials.find((cred) => typeof cred === "object" && cred["@context"].includes(VCDM_CREDENTIAL_CONTEXT_V2)) ? VCDM_CREDENTIAL_CONTEXT_V2 : void 0; const presentationContext = addVcdmContextIfNeeded(args?.presentation?.["@context"] ?? [], v2Credential ?? v1Credential ?? VCDM_CREDENTIAL_CONTEXT_V2); const presentationType = processEntryToArray(args?.presentation?.type, "VerifiablePresentation"); let issuanceDate = presentation?.validFrom ?? presentation?.issuanceDate ?? (typeof now === "number" ? new Date(now) : now).toISOString(); if (issuanceDate instanceof Date) { issuanceDate = issuanceDate.toISOString(); } const presentationPayload = { ...presentation, "@context": presentationContext, type: presentationType, ...v1Credential && { issuanceDate }, ...v2Credential && { validFrom: issuanceDate } }; if (!isDefined(presentationPayload.holder) || !presentationPayload.holder) { throw new Error("invalid_argument: args.presentation.holderDID must not be empty"); } if (presentationPayload.verifiableCredential) { presentationPayload.verifiableCredential = presentationPayload.verifiableCredential.map((cred) => { if (typeof cred !== "string" && cred.proof.jwt) { return cred.proof.jwt; } else { return cred; } }); } return { presentation: presentationPayload, holder: removeDIDParameters(presentationPayload.holder) }; } __name(preProcessPresentation, "preProcessPresentation"); // src/vcdmCredentialPlugin.ts var debug2 = Debug2("sphereon:ssi-sdk:vcdm"); var VcdmCredentialPlugin = class { static { __name(this, "VcdmCredentialPlugin"); } methods; schema = { components: { schemas: { ...schema.ICredentialIssuer.components.schemas, ...schema.ICredentialVerifier.components.schemas }, methods: { ...schema.ICredentialIssuer.components.methods, ...schema.ICredentialVerifier.components.methods } } }; issuers; constructor(options) { this.issuers = options.issuers; this.methods = { listUsableProofFormats: this.listUsableProofFormats.bind(this), createVerifiableCredential: this.createVerifiableCredential.bind(this), verifyCredential: this.verifyCredential.bind(this), createVerifiablePresentation: this.createVerifiablePresentation.bind(this), verifyPresentation: this.verifyPresentation.bind(this) }; } async listUsableProofFormats(did, context) { const signingOptions = []; const keys = did.keys; for (const key of keys) { for (const issuer of this.issuers) { if (issuer.matchKeyForType(key)) { signingOptions.push(issuer.getTypeProofFormat()); } } } return signingOptions; } /** {@inheritdoc @veramo/core#ICredentialIssuer.createVerifiableCredential} */ async createVerifiableCredential(args, context) { let { proofFormat /* keyRef, removeOriginalFields, now , ...otherOptions */ } = args; const { credential, issuer, now } = preProcessCredentialPayload(args); try { await context.agent.didManagerGet({ did: issuer }); } catch (e) { throw new Error(`invalid_argument: credential.issuer must be a DID managed by this agent. ${e}`); } try { async function findAndIssueCredential(issuers) { for (const issuer2 of issuers) { if (issuer2.canIssueCredentialType({ proofFormat })) { return await issuer2.createVerifiableCredential({ ...args, credential, now }, context); } } throw new Error(`invalid_setup: No issuer found for the requested proof format: ${proofFormat}, supported: ${issuers.map((i) => i.getTypeProofFormat()).join(",")}`); } __name(findAndIssueCredential, "findAndIssueCredential"); const verifiableCredential = await findAndIssueCredential(this.issuers); return verifiableCredential; } catch (error) { debug2(error); return Promise.reject(error); } } /** {@inheritdoc @veramo/core#ICredentialVerifier.verifyCredential} */ async verifyCredential(args, context) { let { credential, policies /*, ...otherOptions*/ } = args; let verifiedCredential; let verificationResult; async function findAndVerifyCredential(issuers) { for (const issuer of issuers) { if (issuer.canVerifyDocumentType({ document: credential })) { return issuer.verifyCredential(args, context); } } const uniform = CredentialMapper.toUniformCredential(args.credential); return Promise.reject(Error(`invalid_setup: No verifier found for the provided credential credential type: ${JSON.stringify(uniform.type)} proof type ${asArray2(uniform.proof)?.[0]?.type} supported: ${issuers.map((i) => i.getTypeProofFormat()).join(",")}`)); } __name(findAndVerifyCredential, "findAndVerifyCredential"); verificationResult = await findAndVerifyCredential(this.issuers); verifiedCredential = credential; if (policies?.credentialStatus !== false && await isRevoked(verifiedCredential, context)) { const results = verificationResult.results; const partialSingleResult = Array.isArray(results) ? results[0] : { credential, verified: false, log: [] }; const result = { ...partialSingleResult, credential, verified: false, error: { message: "revoked: The credential was revoked by the issuer", errorCode: "revoked" }, log: [ ...partialSingleResult.log ?? [], { id: "revocation_status", valid: false } ] }; verificationResult = { ...verificationResult, verified: false, error: result.error, results: [ result ] }; } return verificationResult; } /** {@inheritdoc @veramo/core#ICredentialIssuer.createVerifiablePresentation} */ async createVerifiablePresentation(args, context) { const { proofFormat } = args; const { presentation } = preProcessPresentation(args); let verifiablePresentation; async function findAndCreatePresentation(issuers) { for (const issuer of issuers) { if (issuer.canIssueCredentialType({ proofFormat })) { return await issuer.createVerifiablePresentation({ ...args, presentation }, context); } } throw new Error(`invalid_setup: No issuer found for the requested proof format: ${proofFormat}, supported: ${issuers.map((i) => i.getTypeProofFormat()).join(",")}`); } __name(findAndCreatePresentation, "findAndCreatePresentation"); verifiablePresentation = await findAndCreatePresentation(this.issuers); return verifiablePresentation; } /** {@inheritdoc @veramo/core#ICredentialVerifier.verifyPresentation} */ async verifyPresentation(args, context) { let { presentation /*domain, challenge, fetchRemoteContexts, policies, ...otherOptions*/ } = args; async function findAndVerifyPresentation(issuers) { for (const issuer of issuers) { if (issuer.canVerifyDocumentType({ document: presentation })) { return issuer.verifyPresentation(args, context); } } throw new Error("invalid_setup: No verifier found for the provided presentation"); } __name(findAndVerifyPresentation, "findAndVerifyPresentation"); const result = await findAndVerifyPresentation(this.issuers); return result; } }; // src/index.ts var CredentialIssuer = VcdmCredentialPlugin; export { CredentialIssuer, MessageTypes, VcdmCredentialPlugin, W3cMessageHandler, extractIssuer2 as extractIssuer, isRevoked, pickSigningKey, preProcessCredentialPayload, preProcessPresentation, removeDIDParameters }; //# sourceMappingURL=index.js.map