@sphereon/did-auth-siop
Version:
Self Issued OpenID V2 (SIOPv2) and OpenID 4 Verifiable Presentations (OID4VP)
294 lines • 20.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PresentationExchange = void 0;
const pex_1 = require("@sphereon/pex");
const ssi_types_1 = require("@sphereon/ssi-types");
const helpers_1 = require("../helpers");
const types_1 = require("../types");
const types_2 = require("./types");
class PresentationExchange {
constructor(opts) {
this.allDIDs = opts.allDIDs;
this.allVerifiableCredentials = opts.allVerifiableCredentials;
this.pex = new pex_1.PEX({ hasher: opts.hasher });
}
/**
* Construct presentation submission from selected credentials
* @param presentationDefinition payload object received by the OP from the RP
* @param selectedCredentials
* @param presentationSignCallback
* @param options
*/
createVerifiablePresentation(presentationDefinition, selectedCredentials, presentationSignCallback,
// options2?: { nonce?: string; domain?: string, proofType?: IProofType, verificationMethod?: string, signatureKeyEncoding?: KeyEncoding },
options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f;
if (!presentationDefinition) {
throw new Error(types_1.SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID);
}
const signOptions = Object.assign(Object.assign({}, options), { presentationSubmissionLocation: pex_1.PresentationSubmissionLocation.EXTERNAL, proofOptions: Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.proofOptions), { proofPurpose: (_b = (_a = options === null || options === void 0 ? void 0 : options.proofOptions) === null || _a === void 0 ? void 0 : _a.proofPurpose) !== null && _b !== void 0 ? _b : ssi_types_1.IProofPurpose.authentication, type: (_d = (_c = options === null || options === void 0 ? void 0 : options.proofOptions) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : ssi_types_1.IProofType.EcdsaSecp256k1Signature2019 }), signatureOptions: Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.signatureOptions), {
// verificationMethod: options?.signatureOptions?.verificationMethod,
keyEncoding: (_f = (_e = options === null || options === void 0 ? void 0 : options.signatureOptions) === null || _e === void 0 ? void 0 : _e.keyEncoding) !== null && _f !== void 0 ? _f : pex_1.KeyEncoding.Hex }) });
// When there are MDoc credentials among the selected ones, filter those out as pex does not support mdoc credentials
const filteredCredentials = this.removeMDocCredentials(selectedCredentials);
return yield this.pex.verifiablePresentationFrom(presentationDefinition, filteredCredentials, presentationSignCallback, signOptions);
});
}
removeMDocCredentials(selectedCredentials) {
return selectedCredentials.filter((vc) => !ssi_types_1.CredentialMapper.isMsoMdocDecodedCredential(vc) && !ssi_types_1.CredentialMapper.isMsoMdocOid4VPEncoded(vc));
}
/**
* This method will be called from the OP when we are certain that we have a
* PresentationDefinition object inside our requestPayload
* Finds a set of `VerifiableCredential`s from a list supplied to this class during construction,
* matching presentationDefinition object found in the requestPayload
* if requestPayload doesn't contain any valid presentationDefinition throws an error
* if PEX library returns any error in the process, throws the error
* returns the SelectResults object if successful
* @param presentationDefinition object received by the OP from the RP
* @param opts
*/
selectVerifiableCredentialsForSubmission(presentationDefinition, opts) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
if (!presentationDefinition) {
throw new Error(types_1.SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID);
}
else if (!this.allVerifiableCredentials || this.allVerifiableCredentials.length == 0) {
throw new Error(`${types_1.SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, no VCs were provided`);
}
const selectResults = this.pex.selectFrom(presentationDefinition, this.allVerifiableCredentials, Object.assign(Object.assign({}, opts), { holderDIDs: (_a = opts === null || opts === void 0 ? void 0 : opts.holderDIDs) !== null && _a !== void 0 ? _a : this.allDIDs,
// fixme limited disclosure
limitDisclosureSignatureSuites: [] }));
if (selectResults.areRequiredCredentialsPresent === pex_1.Status.ERROR) {
throw new Error(`message: ${types_1.SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, details: ${JSON.stringify(selectResults.errors)}`);
}
return selectResults;
});
}
/**
* validatePresentationAgainstDefinition function is called mainly by the RP
* after receiving the VP from the OP
* @param presentationDefinition object containing PD
* @param verifiablePresentation
* @param opts
*/
static validatePresentationAgainstDefinition(presentationDefinition, verifiablePresentation, opts) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const wvp = typeof verifiablePresentation === 'object' && 'original' in verifiablePresentation
? verifiablePresentation
: ssi_types_1.CredentialMapper.toWrappedVerifiablePresentation(verifiablePresentation);
if (!presentationDefinition) {
throw new Error(types_1.SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID);
}
else if (!wvp ||
!wvp.presentation ||
(ssi_types_1.CredentialMapper.isWrappedW3CVerifiablePresentation(wvp) &&
(!wvp.presentation.verifiableCredential || wvp.presentation.verifiableCredential.length === 0))) {
throw new Error(types_1.SIOPErrors.NO_VERIFIABLE_PRESENTATION_NO_CREDENTIALS);
}
const evaluationResults = new pex_1.PEX({ hasher: opts === null || opts === void 0 ? void 0 : opts.hasher }).evaluatePresentation(presentationDefinition, wvp.original, opts);
if ((_a = evaluationResults.errors) === null || _a === void 0 ? void 0 : _a.length) {
throw new Error(`message: ${types_1.SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, details: ${JSON.stringify(evaluationResults.errors)}`);
}
return evaluationResults;
});
}
static assertValidPresentationSubmission(presentationSubmission) {
const validationResult = pex_1.PEX.validateSubmission(presentationSubmission);
if ((Array.isArray(validationResult) && validationResult[0].message != 'ok') ||
(!Array.isArray(validationResult) && validationResult.message != 'ok')) {
throw new Error(`${types_1.SIOPErrors.RESPONSE_OPTS_PRESENTATIONS_SUBMISSION_IS_NOT_VALID}, details ${JSON.stringify(validationResult)}`);
}
}
/**
* Finds a valid PresentationDefinition inside the given AuthenticationRequestPayload
* throws exception if the PresentationDefinition is not valid
* returns null if no property named "presentation_definition" is found
* returns a PresentationDefinition if a valid instance found
* @param authorizationRequestPayload object that can have a presentation_definition inside
* @param version
*/
static findValidPresentationDefinitions(authorizationRequestPayload, version) {
return __awaiter(this, void 0, void 0, function* () {
const allDefinitions = [];
function extractDefinitionFromVPToken() {
return __awaiter(this, void 0, void 0, function* () {
const vpTokens = (0, helpers_1.extractDataFromPath)(authorizationRequestPayload, '$..vp_token.presentation_definition').map((d) => d.value);
const vpTokenRefs = (0, helpers_1.extractDataFromPath)(authorizationRequestPayload, '$..vp_token.presentation_definition_uri');
if (vpTokens && vpTokens.length && vpTokenRefs && vpTokenRefs.length) {
throw new Error(types_1.SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE);
}
if (vpTokens && vpTokens.length) {
vpTokens.forEach((vpToken) => {
if (allDefinitions.find((value) => value.definition.id === vpToken.id)) {
console.log(`Warning. We encountered presentation definition with id ${vpToken.id}, more then once whilst processing! Make sure your payload is valid!`);
return;
}
PresentationExchange.assertValidPresentationDefinition(vpToken);
allDefinitions.push({
definition: vpToken,
location: types_2.PresentationDefinitionLocation.CLAIMS_VP_TOKEN,
version,
});
});
}
else if (vpTokenRefs && vpTokenRefs.length) {
for (const vpTokenRef of vpTokenRefs) {
const pd = (yield (0, helpers_1.getWithUrl)(vpTokenRef.value));
if (allDefinitions.find((value) => value.definition.id === pd.id)) {
console.log(`Warning. We encountered presentation definition with id ${pd.id}, more then once whilst processing! Make sure your payload is valid!`);
return;
}
PresentationExchange.assertValidPresentationDefinition(pd);
allDefinitions.push({ definition: pd, location: types_2.PresentationDefinitionLocation.CLAIMS_VP_TOKEN, version });
}
}
});
}
function addSingleToplevelPDToPDs(definition, version) {
if (allDefinitions.find((value) => value.definition.id === definition.id)) {
console.log(`Warning. We encountered presentation definition with id ${definition.id}, more then once whilst processing! Make sure your payload is valid!`);
return;
}
PresentationExchange.assertValidPresentationDefinition(definition);
allDefinitions.push({
definition,
location: types_2.PresentationDefinitionLocation.TOPLEVEL_PRESENTATION_DEF,
version,
});
}
function extractDefinitionFromTopLevelDefinitionProperty(version) {
return __awaiter(this, void 0, void 0, function* () {
const definitions = (0, helpers_1.extractDataFromPath)(authorizationRequestPayload, '$.presentation_definition');
const definitionsFromList = (0, helpers_1.extractDataFromPath)(authorizationRequestPayload, '$.presentation_definition[*]');
const definitionRefs = (0, helpers_1.extractDataFromPath)(authorizationRequestPayload, '$.presentation_definition_uri');
const definitionRefsFromList = (0, helpers_1.extractDataFromPath)(authorizationRequestPayload, '$.presentation_definition_uri[*]');
const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0);
const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0);
if (hasPD && hasPdRef) {
throw new Error(types_1.SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE);
}
if (definitions && definitions.length > 0) {
definitions.forEach((definition) => {
addSingleToplevelPDToPDs(definition.value, version);
});
}
else if (definitionsFromList && definitionsFromList.length > 0) {
definitionsFromList.forEach((definition) => {
addSingleToplevelPDToPDs(definition.value, version);
});
}
else if (definitionRefs && definitionRefs.length > 0) {
for (const definitionRef of definitionRefs) {
const pd = yield (0, helpers_1.getWithUrl)(definitionRef.value);
addSingleToplevelPDToPDs(pd, version);
}
}
else if (definitionsFromList && definitionRefsFromList.length > 0) {
for (const definitionRef of definitionRefsFromList) {
const pd = yield (0, helpers_1.getWithUrl)(definitionRef.value);
addSingleToplevelPDToPDs(pd, version);
}
}
});
}
if (authorizationRequestPayload) {
if (!version || version < types_1.SupportedVersion.SIOPv2_D11) {
yield extractDefinitionFromVPToken();
}
yield extractDefinitionFromTopLevelDefinitionProperty();
}
return allDefinitions;
});
}
static assertValidPresentationDefinitionWithLocations(definitionsWithLocations) {
if (definitionsWithLocations && definitionsWithLocations.length > 0) {
definitionsWithLocations.forEach((definitionWithLocation) => PresentationExchange.assertValidPresentationDefinition(definitionWithLocation.definition));
}
}
static assertValidPresentationDefinition(presentationDefinition) {
const validationResult = pex_1.PEX.validateDefinition(presentationDefinition);
if ((Array.isArray(validationResult) && validationResult[0].message != 'ok') ||
(!Array.isArray(validationResult) && validationResult.message != 'ok')) {
throw new Error(`${types_1.SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_NOT_VALID}`);
}
}
static validatePresentationsAgainstDefinitions(definitions, vpPayloads, verifyPresentationCallback, opts) {
return __awaiter(this, void 0, void 0, function* () {
if (!definitions || !vpPayloads || (Array.isArray(vpPayloads) && vpPayloads.length === 0) || !definitions.length) {
throw new Error(types_1.SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD);
}
yield Promise.all(definitions.map((pd) => __awaiter(this, void 0, void 0, function* () { return yield PresentationExchange.validatePresentationsAgainstDefinition(pd.definition, vpPayloads, verifyPresentationCallback, opts); })));
});
}
static validatePresentationsAgainstDefinition(definition, vpPayloads, verifyPresentationCallback, opts) {
return __awaiter(this, void 0, void 0, function* () {
const pex = new pex_1.PEX({ hasher: opts === null || opts === void 0 ? void 0 : opts.hasher });
const vpPayloadsArray = Array.isArray(vpPayloads) ? vpPayloads : [vpPayloads];
let evaluationResults = undefined;
if (opts === null || opts === void 0 ? void 0 : opts.presentationSubmission) {
evaluationResults = pex.evaluatePresentation(definition,
// It's important the structure matches what we received so it can be correctly matched against the submission
Array.isArray(vpPayloads) ? vpPayloads.map((wvp) => wvp.original) : vpPayloads.original, Object.assign(Object.assign({}, opts), { presentationSubmissionLocation: pex_1.PresentationSubmissionLocation.EXTERNAL }));
}
else {
for (const wvp of vpPayloadsArray) {
if (ssi_types_1.CredentialMapper.isWrappedW3CVerifiablePresentation(wvp) && wvp.presentation.presentation_submission) {
const presentationSubmission = wvp.presentation.presentation_submission;
evaluationResults = pex.evaluatePresentation(definition, wvp.original, Object.assign(Object.assign({}, opts), { presentationSubmission, presentationSubmissionLocation: pex_1.PresentationSubmissionLocation.PRESENTATION }));
const submission = evaluationResults.value;
// Found valid submission
if (evaluationResults.areRequiredCredentialsPresent && submission && submission.definition_id === definition.id)
break;
}
}
}
if (!evaluationResults) {
throw new Error(types_1.SIOPErrors.NO_PRESENTATION_SUBMISSION);
}
if (evaluationResults.areRequiredCredentialsPresent === pex_1.Status.ERROR ||
(evaluationResults.errors && evaluationResults.errors.length > 0) ||
!evaluationResults.value) {
throw new Error(`message: ${types_1.SIOPErrors.COULD_NOT_FIND_VCS_MATCHING_PD}, details: ${JSON.stringify(evaluationResults.errors)}`);
}
if (evaluationResults.value.definition_id !== definition.id) {
throw new Error(`${types_1.SIOPErrors.PRESENTATION_SUBMISSION_DEFINITION_ID_DOES_NOT_MATCHING_DEFINITION_ID}. submission.definition_id: ${evaluationResults.value.definition_id}, definition.id: ${definition.id}`);
}
const presentationsToVerify = evaluationResults.presentations;
// The verifyPresentationCallback function is mandatory for RP only,
// So the behavior here is to bypass it if not present
if (verifyPresentationCallback && evaluationResults.value !== undefined) {
// Verify the signature of all VPs
yield Promise.all(presentationsToVerify.map((presentation) => __awaiter(this, void 0, void 0, function* () {
let verificationResult;
try {
verificationResult = yield verifyPresentationCallback(presentation, evaluationResults.value);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`${types_1.SIOPErrors.VERIFIABLE_PRESENTATION_SIGNATURE_NOT_VALID}: ${errorMessage}`);
}
if (!verificationResult.verified) {
throw new Error(types_1.SIOPErrors.VERIFIABLE_PRESENTATION_SIGNATURE_NOT_VALID + (verificationResult.reason ? `. ${verificationResult.reason}` : ''));
}
})));
}
PresentationExchange.assertValidPresentationSubmission(evaluationResults.value);
return evaluationResults;
});
}
}
exports.PresentationExchange = PresentationExchange;
//# sourceMappingURL=PresentationExchange.js.map