Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/mdoc-parsing #185

Merged
merged 19 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
IVerifiablePresentation,
OriginalVerifiableCredential,
SdJwtDecodedVerifiableCredential,
WrappedMdocCredential,
WrappedVerifiableCredential,
WrappedVerifiablePresentation,
} from '@sphereon/ssi-types';
Expand Down Expand Up @@ -416,6 +417,8 @@
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 @@

// 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 @@
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;

Check warning on line 594 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L594

Added line #L594 was not covered by tests
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;

Check warning on line 600 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L600

Added line #L600 was not covered by tests
}

// 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;

Check warning on line 614 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L614

Added line #L614 was not covered by tests
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;

Check warning on line 624 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L624

Added line #L624 was not covered by tests
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionFormatNoMatch',
message: `The VP at path ${descriptor.path} does not match the required format ${descriptor.format}`,
});
continue;

Check warning on line 630 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L630

Added line #L630 was not covered by tests
}
} 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 @@
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 @@
});
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;

Check warning on line 671 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L670-L671

Added lines #L670 - L671 were not covered by tests
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;

Check warning on line 677 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L677

Added line #L677 was not covered by tests
}

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 @@

// 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 @@
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 @@
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 @@
}
}
} else {
throw new Error(`Unsupported format for selective disclosure ${wvc.format}`);
throw new Error('Unsupported format for selective disclosure');

Check warning on line 129 in lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/handlers/limitDisclosureEvaluationHandler.ts#L129

Added line #L129 was not covered by tests
}
}

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
Loading