@animo-id/pex
Version:
A Typescript implementation of the v1 and v2 DIF Presentation Exchange specification
203 lines • 22.4 kB
JavaScript
import { Optionality } from '@sphereon/pex-models';
import { Status } from '../../ConstraintUtils';
import PexMessages from '../../types/Messages';
import { PexCredentialMapper } from '../../types/PexCredentialMapper';
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());
if (eligibleInputDescriptors.length > 0 && this.isLimitDisclosureSupported(eligibleInputDescriptors, wvc, vcIndex)) {
this.enforceLimitDisclosure(wrappedVcs, eligibleInputDescriptors, vcIndex);
}
});
}
enforceLimitDisclosure(wrappedVcs, eligibleInputDescriptors, vcIndex) {
const wvc = wrappedVcs[vcIndex];
if (PexCredentialMapper.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 = PexCredentialMapper.isSdJwtDecodedCredential(wvc.original) ? wvc.credential : wvc.credential.compactSdJwtVc;
for (const { inputDescriptorIndex, inputDescriptor } of eligibleInputDescriptors) {
this.createSuccessResult(inputDescriptorIndex, `$[${vcIndex}]`, inputDescriptor.constraints?.limit_disclosure);
}
}
}
else if (PexCredentialMapper.isWrappedMdocCredential(wvc)) {
for (const { inputDescriptorIndex, inputDescriptor } of eligibleInputDescriptors) {
this.createSuccessResult(inputDescriptorIndex, `$[${vcIndex}]`, inputDescriptor.constraints?.limit_disclosure);
}
}
else if (PexCredentialMapper.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 if (!('optional' in field && field.optional)) {
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 if (!('optional' in field && field.optional)) {
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 || limitDisclosure === Optionality.Preferred ? 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGltaXREaXNjbG9zdXJlRXZhbHVhdGlvbkhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9saWIvZXZhbHVhdGlvbi9oYW5kbGVycy9saW1pdERpc2Nsb3N1cmVFdmFsdWF0aW9uSGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQXdDLFdBQVcsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBV3pGLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUUvQyxPQUFPLFdBQVcsTUFBTSxzQkFBc0IsQ0FBQztBQUMvQyxPQUFPLEVBQUUsbUJBQW1CLEVBQStCLE1BQU0saUNBQWlDLENBQUM7QUFDbkcsT0FBTyxFQUFFLHlCQUF5QixFQUFFLGFBQWEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUd2RSxPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUN4RSxPQUFPLEVBQUUsb0NBQW9DLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUU1RixNQUFNLE9BQU8sZ0NBQWlDLFNBQVEseUJBQXlCO0lBQzdFLFlBQVksTUFBd0I7UUFDbEMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2hCLENBQUM7SUFFTSxPQUFPO1FBQ1osT0FBTywyQkFBMkIsQ0FBQztJQUNyQyxDQUFDO0lBRU0sTUFBTSxDQUFDLEVBQW1DLEVBQUUsVUFBeUM7UUFDMUYsSUFBSSxDQUFDLHVCQUF1QixDQUFDLEVBQUUsQ0FBQyxpQkFBd0MsRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN4RixDQUFDO0lBRU8sMEJBQTBCLENBQ2hDLHdCQUFvRCxFQUNwRCxHQUFnQyxFQUNoQyxPQUFlO1FBRWYsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLFdBQVcsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLFVBQVU7WUFBRSxPQUFPLElBQUksQ0FBQztRQUN6RSxJQUFJLEdBQUcsQ0FBQyxNQUFNLEtB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