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

feat: mdoc selectFrom, evaluation and parsing #179

Merged
Show file tree
Hide file tree
Changes from 4 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
67 changes: 40 additions & 27 deletions lib/evaluation/evaluationClientWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,12 +571,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 +587,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 @@ -634,8 +645,8 @@ 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) {
} else if (descriptor.format === 'vc+sd-jwt' || descriptor.format === 'mso_mdoc') {
if (!matchingVp.vcs || !matchingVp.vcs.length) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
Expand All @@ -644,18 +655,18 @@ export class EvaluationClientWrapper {
});
continue;
}
vc = vp.vcs[0];
vc = matchingVp.vcs[0];
} 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 +682,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
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": "file:/Users/timo/Developer/Work/Projects/Animo/Sphereon/ssi-sdk/packages/ssi-types",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ssi-sdk version is 0.30.2-next.129

"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"jwt-decode": "^3.1.2",
Expand Down
Loading
Loading