Skip to content

Commit

Permalink
Merge pull request #185 from Sphereon-Opensource/feature/mdoc-parsing
Browse files Browse the repository at this point in the history
feature/mdoc-parsing
  • Loading branch information
sanderPostma authored Nov 1, 2024
2 parents 772a058 + 4cabdd3 commit 9596f9d
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 1,500 deletions.
87 changes: 61 additions & 26 deletions lib/evaluation/evaluationClientWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IVerifiablePresentation,
OriginalVerifiableCredential,
SdJwtDecodedVerifiableCredential,
WrappedMdocCredential,
WrappedVerifiableCredential,
WrappedVerifiablePresentation,
} from '@sphereon/ssi-types';
Expand Down Expand Up @@ -416,6 +417,8 @@ export class EvaluationClientWrapper {
warnings: [],
};

// Reset and configure the evaluation client on each iteration
this._client = new EvaluationClient();
this._client.evaluate(pd, allWvcs, opts);
result.warnings = this.formatNotInfo(Status.WARN);
result.errors = this.formatNotInfo(Status.ERROR);
Expand Down Expand Up @@ -571,12 +574,12 @@ export class EvaluationClientWrapper {

// Iterate over each descriptor in the submission
for (const [descriptorIndex, descriptor] of submission.descriptor_map.entries()) {
let matchingVps: WrappedVerifiablePresentation[] = [];
let matchingVp: WrappedVerifiablePresentation;

if (presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL) {
// Extract VPs matching the descriptor path
const vpResults = JsonPathUtils.extractInputField(wvps, [descriptor.path]) as Array<{
value: WrappedVerifiablePresentation[];
value: WrappedVerifiablePresentation;
}>;

if (!vpResults.length) {
Expand All @@ -587,45 +590,56 @@ export class EvaluationClientWrapper {
message: `Unable to extract path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] from presentation(s)`,
});
continue;
} else if (vpResults.length > 1) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionPathMultipleEntries',
message: `Extraction of path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] resulted in multiple values being returned.`,
});
continue;
}

// Flatten the array of VPs
const allVps = vpResults.flatMap((vpResult) => vpResult.value);

// Filter VPs that match the required format
matchingVps = allVps.filter((vp) => vp.format === descriptor.format);

if (!matchingVps.length) {
matchingVp = vpResults[0].value;
if (Array.isArray(matchingVp)) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionFormatNoMatch',
message: `No VP at path ${descriptor.path} matches the required format ${descriptor.format}`,
tag: 'SubmissionPathMultipleEntries',
message: `Extraction of path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] returned multiple entires. This is probably because the submission uses '$' to reference the presentation, while an array was used (thus all presentations are selected). Make sure the submission uses the correct path.`,
});
continue;
}
if (!matchingVp) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionPathNotFound',
message: `Extraction of path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] succeeded, but the value was undefined.`,
});
continue;
}

// Log a warning if multiple VPs match the descriptor
if (matchingVps.length > 1) {
result.warnings?.push({
status: Status.WARN,
tag: 'MultipleVpsMatched',
message: `Multiple VPs matched for descriptor_path[${descriptorIndex}]. Using the first matching VP.`,
if (matchingVp.format !== descriptor.format) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionFormatNoMatch',
message: `The VP at path ${descriptor.path} does not match the required format ${descriptor.format}`,
});
continue;
}
} else {
// When submission location is PRESENTATION, assume a single VP
matchingVps = Array.isArray(wvps) ? [wvps[0]] : [wvps];
matchingVp = Array.isArray(wvps) ? wvps[0] : wvps;
}

// Process the first matching VP
const vp = matchingVps[0];
let vc: WrappedVerifiableCredential;
let vcPath: string = `presentation ${descriptor.path}`;

if (presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL) {
if (descriptor.path_nested) {
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor.path_nested, descriptorIndex.toString(), vp);
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor.path_nested, descriptorIndex.toString(), matchingVp);
if (extractionResult.error) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push(extractionResult.error);
Expand All @@ -635,7 +649,7 @@ export class EvaluationClientWrapper {
vc = extractionResult.wvc;
vcPath += ` with nested credential ${descriptor.path_nested.path}`;
} else if (descriptor.format === 'vc+sd-jwt') {
if (!vp.vcs || !vp.vcs.length) {
if (!matchingVp.vcs || !matchingVp.vcs.length) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
Expand All @@ -644,18 +658,37 @@ export class EvaluationClientWrapper {
});
continue;
}
vc = vp.vcs[0];
vc = matchingVp.vcs[0];
} else if (descriptor.format === 'mso_mdoc') {
// We already know the format is mso_mdoc so this cast is safe
const vcs = matchingVp.vcs as WrappedMdocCredential[];
vcPath += ` with nested mdoc with doctype ${descriptor.id}`;

const matchingVc = vcs.find((vc) => descriptor.id === vc.credential.docType.asStr);

if (!matchingVc) {
const allDoctypes = vcs.map((vc) => `'${vc.credential.docType.asStr}'`).join(', ');
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'NoCredentialsFound',
message: `No mdoc credential with doctype '${descriptor.id}' found in mdoc vp. Available documents are ${allDoctypes}`,
});
continue;
}

vc = matchingVc;
} else {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'UnsupportedFormat',
message: `VP format ${vp.format} is not supported`,
message: `VP format ${matchingVp.format} is not supported`,
});
continue;
}
} else {
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor, descriptorIndex.toString(), vp);
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor, descriptorIndex.toString(), matchingVp);
if (extractionResult.error) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push(extractionResult.error);
Expand All @@ -671,7 +704,9 @@ export class EvaluationClientWrapper {

// Determine holder DIDs
const holderDIDs =
CredentialMapper.isW3cPresentation(vp.presentation) && vp.presentation.holder ? [vp.presentation.holder] : opts?.holderDIDs || [];
CredentialMapper.isW3cPresentation(matchingVp.presentation) && matchingVp.presentation.holder
? [matchingVp.presentation.holder]
: opts?.holderDIDs || [];

if (pd.input_descriptors.findIndex((_id) => _id.id === descriptor.id) === -1) {
result.areRequiredCredentialsPresent = Status.ERROR;
Expand Down
9 changes: 4 additions & 5 deletions lib/evaluation/handlers/didRestrictionEvaluationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,10 @@ export class DIDRestrictionEvaluationHandler extends AbstractEvaluationHandler {
return typeof wrappedVc.credential.issuer === 'object' ? wrappedVc.credential.issuer.id : wrappedVc.credential.issuer;
} else if (CredentialMapper.isSdJwtDecodedCredential(wrappedVc.credential)) {
return wrappedVc.credential.decodedPayload.iss;
} else if (CredentialMapper.isWrappedMdocCredential(wrappedVc)) {
if (typeof wrappedVc.decoded === 'object' && wrappedVc.decoded.iss !== undefined) {
return wrappedVc.decoded.iss;
}
throw new Error('cannot get issuer from the supplied mdoc credential');
}
// mdoc is not bound to did
else if (CredentialMapper.isWrappedMdocCredential(wrappedVc)) {
return undefined;
}
throw new Error('Unsupported credential type');
}
Expand Down
11 changes: 8 additions & 3 deletions lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class LimitDisclosureEvaluationHandler extends AbstractEvaluationHandler
wvc: WrappedVerifiableCredential,
vcIndex: number,
): boolean {
if (wvc.format === 'vc+sd-jwt') return true;
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 as IVerifiableCredential;
Expand Down Expand Up @@ -109,7 +110,11 @@ export class LimitDisclosureEvaluationHandler extends AbstractEvaluationHandler
this.createSuccessResult(inputDescriptorIndex, `$[${vcIndex}]`, inputDescriptor.constraints?.limit_disclosure);
}
}
} else if (CredentialMapper.isW3cCredential(wvc.credential)) {
} 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.
Expand All @@ -121,7 +126,7 @@ export class LimitDisclosureEvaluationHandler extends AbstractEvaluationHandler
}
}
} else {
throw new Error(`Unsupported format for selective disclosure ${wvc.format}`);
throw new Error('Unsupported format for selective disclosure');
}
}

Expand Down
3 changes: 2 additions & 1 deletion lib/evaluation/handlers/uriEvaluationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CredentialMapper,
ICredential,
ICredentialSchema,
MdocDocument,
OriginalType,
SdJwtDecodedVerifiableCredential,
WrappedVerifiableCredential,
Expand Down Expand Up @@ -125,7 +126,7 @@ export class UriEvaluationHandler extends AbstractEvaluationHandler {
}
}

private static buildVcContextAndSchemaUris(credential: ICredential | SdJwtDecodedVerifiableCredential, version: PEVersion) {
private static buildVcContextAndSchemaUris(credential: ICredential | SdJwtDecodedVerifiableCredential | MdocDocument, version: PEVersion) {
const uris: string[] = [];

// W3C credential
Expand Down
1 change: 1 addition & 0 deletions lib/utils/formatMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ const vcVpFormatMap = {
ldp_vc: 'ldp_vp',
jwt_vc: 'jwt_vp',
'vc+sd-jwt': 'vc+sd-jwt',
mso_mdoc: 'mso_mdoc',
} as const;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@sd-jwt/present": "^0.7.2",
"@sd-jwt/types": "^0.7.2",
"@sphereon/pex-models": "^2.3.1",
"@sphereon/ssi-types": "0.30.1",
"@sphereon/ssi-types": "0.30.2-next.135",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"jwt-decode": "^3.1.2",
Expand Down
Loading

0 comments on commit 9596f9d

Please sign in to comment.