UNPKG

@sphereon/pex

Version:

A Typescript implementation of the v1 and v2 DIF Presentation Exchange specification

204 lines 22.6 kB
import { Optionality } from '@sphereon/pex-models'; import { CredentialMapper, } from '@sphereon/ssi-types'; import { Status } from '../../ConstraintUtils'; import PexMessages from '../../types/Messages'; import { applySdJwtLimitDisclosure, JsonPathUtils } from '../../utils'; import { AbstractEvaluationHandler } from './abstractEvaluationHandler'; import { eligibleInputDescriptorsForWrappedVc } from './markForSubmissionEvaluationHandler'; export class LimitDisclosureEvaluationHandler extends AbstractEvaluationHandler { constructor(client) { super(client); } getName() { return 'LimitDisclosureEvaluation'; } handle(pd, wrappedVcs) { this.evaluateLimitDisclosure(pd.input_descriptors, wrappedVcs); } isLimitDisclosureSupported(eligibleInputDescriptors, wvc, vcIndex) { if (wvc.format === 'vc+sd-jwt' || wvc.format === 'mso_mdoc') return true; if (wvc.format === 'ldp' || wvc.format === 'jwt') return false; const limitDisclosureSignatures = this.client.limitDisclosureSignatureSuites; const decoded = wvc.decoded; const proofs = Array.isArray(decoded.proof) ? decoded.proof : decoded.proof ? [decoded.proof] : undefined; const requiredLimitDisclosureInputDescriptorIds = eligibleInputDescriptors .map(({ inputDescriptor: { constraints }, inputDescriptorIndex }) => constraints?.limit_disclosure === Optionality.Required ? inputDescriptorIndex : undefined) .filter((id) => id !== undefined); if (!proofs || proofs.length === 0 || proofs.length > 1 || !proofs[0].type) { // todo: Support/inspect array based proofs if (requiredLimitDisclosureInputDescriptorIds.length > 0) { this.createLimitDisclosureNotSupportedResult(eligibleInputDescriptors.map((i) => i.inputDescriptorIndex), vcIndex, 'Multiple proofs on verifiable credential not supported for limit disclosure'); } return false; } const proof = proofs[0]; const signatureSuite = proof.cryptosuite ? `${proof.type}.${proof.cryptosuite}` : proof.type; if (!limitDisclosureSignatures?.includes(signatureSuite)) { if (requiredLimitDisclosureInputDescriptorIds.length > 0) { this.createLimitDisclosureNotSupportedResult(requiredLimitDisclosureInputDescriptorIds, vcIndex, `Signature suite '${signatureSuite}' is not present in limitDisclosureSignatureSuites [${limitDisclosureSignatures.join(',')}]`); } return false; } return true; } evaluateLimitDisclosure(inputDescriptors, wrappedVcs) { wrappedVcs.forEach((wvc, vcIndex) => { const eligibleInputDescriptors = eligibleInputDescriptorsForWrappedVc(inputDescriptors, vcIndex, this.getResults()); const includeLimitDisclosure = eligibleInputDescriptors.some(({ inputDescriptor: { constraints } }) => constraints?.limit_disclosure === Optionality.Preferred || constraints?.limit_disclosure === Optionality.Required); if (eligibleInputDescriptors.length > 0 && includeLimitDisclosure && this.isLimitDisclosureSupported(eligibleInputDescriptors, wvc, vcIndex)) { this.enforceLimitDisclosure(wrappedVcs, eligibleInputDescriptors, vcIndex); } }); } enforceLimitDisclosure(wrappedVcs, eligibleInputDescriptors, vcIndex) { const wvc = wrappedVcs[vcIndex]; if (CredentialMapper.isWrappedSdJwtVerifiableCredential(wvc)) { const presentationFrame = this.createSdJwtPresentationFrame(eligibleInputDescriptors, wvc.credential, vcIndex); // We update the SD-JWT to it's presentation format (remove disclosures, update pretty payload, etc..), except // we don't create or include the (optional) KB-JWT yet, this is done when we create the presentation if (presentationFrame) { applySdJwtLimitDisclosure(wvc.credential, presentationFrame); wvc.decoded = wvc.credential.decodedPayload; // We need to overwrite the original, as that is returned in the selectFrom method // But we also want to keep the format of the original credential. wvc.original = CredentialMapper.isSdJwtDecodedCredential(wvc.original) ? wvc.credential : wvc.credential.compactSdJwtVc; for (const { inputDescriptorIndex, inputDescriptor } of eligibleInputDescriptors) { this.createSuccessResult(inputDescriptorIndex, `$[${vcIndex}]`, inputDescriptor.constraints?.limit_disclosure); } } } else if (CredentialMapper.isWrappedMdocCredential(wvc)) { for (const { inputDescriptorIndex, inputDescriptor } of eligibleInputDescriptors) { this.createSuccessResult(inputDescriptorIndex, `$[${vcIndex}]`, inputDescriptor.constraints?.limit_disclosure); } } else if (CredentialMapper.isWrappedW3CVerifiableCredential(wvc)) { const internalCredentialToSend = this.createVcWithRequiredFields(eligibleInputDescriptors, wvc.credential, vcIndex); /* When verifiableCredentialToSend is null/undefined an error is raised, the credential will * remain untouched and the verifiable credential won't be submitted. */ if (internalCredentialToSend) { wvc.credential = internalCredentialToSend; for (const { inputDescriptorIndex, inputDescriptor } of eligibleInputDescriptors) { this.createSuccessResult(inputDescriptorIndex, `$[${vcIndex}]`, inputDescriptor.constraints?.limit_disclosure); } } } else { throw new Error('Unsupported format for selective disclosure'); } } createSdJwtPresentationFrame(inputDescriptors, vc, vcIndex) { // Mapping of key -> true to indicate which values should be disclosed in an SD-JWT // Can be nested array / object const presentationFrame = {}; const processNestedObject = (obj, currentPath, basePath) => { if (obj === null || typeof obj !== 'object') { // For literal values, set the path to true in the presentation frame JsonPathUtils.setValue(presentationFrame, currentPath, true); return; } // For arrays, process each element if (Array.isArray(obj)) { obj.forEach((item, index) => { processNestedObject(item, [...currentPath, index], basePath); }); return; } // For objects, process each child property Object.entries(obj).forEach(([key, value]) => { processNestedObject(value, [...currentPath, key], basePath); }); }; for (const { inputDescriptor, inputDescriptorIndex } of inputDescriptors) { for (const field of inputDescriptor.constraints?.fields ?? []) { if (field.path) { const inputField = JsonPathUtils.extractInputField(vc.decodedPayload, field.path); if (inputField.length > 0) { const selectedField = inputField[0]; const fieldValue = JsonPathUtils.getValue(vc.decodedPayload, selectedField.path); if (fieldValue !== null && typeof fieldValue === 'object') { // For objects, recursively process all nested fields processNestedObject(fieldValue, selectedField.path, selectedField.path); } else { // For literal values, just set the path to true JsonPathUtils.setValue(presentationFrame, selectedField.path, true); } } else { this.createMandatoryFieldNotFoundResult(inputDescriptorIndex, vcIndex, field.path); return undefined; } } } } return presentationFrame; } createVcWithRequiredFields(inputDescriptors, vc, vcIndex) { let credentialToSend = {}; credentialToSend = Object.assign(credentialToSend, vc); credentialToSend.credentialSubject = {}; for (const { inputDescriptor, inputDescriptorIndex } of inputDescriptors) { for (const field of inputDescriptor.constraints?.fields ?? []) { if (field.path) { const inputField = JsonPathUtils.extractInputField(vc, field.path); if (inputField.length > 0) { credentialToSend = this.copyResultPathToDestinationCredential(inputField[0], vc, credentialToSend); } else { this.createMandatoryFieldNotFoundResult(inputDescriptorIndex, vcIndex, field.path); return undefined; } } } } return credentialToSend; } copyResultPathToDestinationCredential(requiredField, internalCredential, internalCredentialToSend) { //TODO: ESSIFI-186 let credentialSubject = { ...internalCredential.credentialSubject }; requiredField.path.forEach((e) => { if (credentialSubject[e]) { credentialSubject = { [e]: credentialSubject[e] }; } }); internalCredentialToSend.credentialSubject = { ...internalCredentialToSend.credentialSubject, ...credentialSubject, }; return internalCredentialToSend; } createSuccessResult(idIdx, path, limitDisclosure) { return this.getResults().push({ input_descriptor_path: `$.input_descriptors[${idIdx}]`, verifiable_credential_path: `${path}`, evaluator: this.getName(), status: limitDisclosure === Optionality.Required ? Status.INFO : Status.WARN, message: PexMessages.LIMIT_DISCLOSURE_APPLIED, payload: undefined, }); } createMandatoryFieldNotFoundResult(idIdx, vcIdx, path) { return this.getResults().push({ input_descriptor_path: `$.input_descriptors[${idIdx}]`, verifiable_credential_path: `$[${vcIdx}]`, evaluator: this.getName(), status: Status.ERROR, message: PexMessages.VERIFIABLE_CREDENTIAL_MANDATORY_FIELD_NOT_PRESENT, payload: path, }); } createLimitDisclosureNotSupportedResult(idIdxs, vcIdx, reason) { return this.getResults().push(...idIdxs.map((idIdx) => ({ input_descriptor_path: `$.input_descriptors[${idIdx}]`, verifiable_credential_path: `$[${vcIdx}]`, evaluator: this.getName(), status: Status.ERROR, message: reason ? `${PexMessages.LIMIT_DISCLOSURE_NOT_SUPPORTED}. ${reason}` : PexMessages.LIMIT_DISCLOSURE_NOT_SUPPORTED, }))); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGltaXREaXNjbG9zdXJlRXZhbHVhdGlvbkhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9saWIvZXZhbHVhdGlvbi9oYW5kbGVycy9saW1pdERpc2Nsb3N1cmVFdmFsdWF0aW9uSGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQXdDLFdBQVcsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3pGLE9BQU8sRUFFTCxnQkFBZ0IsR0FPakIsTUFBTSxxQkFBcUIsQ0FBQztBQUc3QixPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFL0MsT0FBTyxXQUFXLE1BQU0sc0JBQXNCLENBQUM7QUFDL0MsT0FBTyxFQUFFLHlCQUF5QixFQUFFLGFBQWEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUd2RSxPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUN4RSxPQUFPLEVBQUUsb0NBQW9DLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUU1RixNQUFNLE9BQU8sZ0NBQWlDLFNBQVEseUJBQXlCO0lBQzdFLFlBQVksTUFBd0I7UUFDbEMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2hCLENBQUM7SUFFTSxPQUFPO1FBQ1osT0FBTywyQkFBMkIsQ0FBQztJQUNyQyxDQUFDO0lBRU0sTUFBTSxDQUFDLEVBQW1DLEVBQUUsVUFBeUM7UUFDMUYsSUFBSSxDQUFDLHVCQUF1QixDQUFDLEVBQUUsQ0FBQyxpQkFBd0MsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN4RixDQUFDO0lBRU8sMEJBQTBCLENBQ2hDLHdCQUFvRCxFQUNwRCxHQUFnQyxFQUNoQyxPQUFlO1FBRWYsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLFdBQVcsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLFVBQVU7WUFBRSxPQUFPLElBQUksQ0FBQztRQUN6RSxJQUFJLEdBQUcsQ0FBQyxNQUFNLEtB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