UNPKG

@identity.com/verifiable-presentations

Version:

Utility Library to securely handle verifiable presentations

643 lines (642 loc) 32.4 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.VerifiablePresentationManager = void 0; var sjcl = __importStar(require("sjcl")); var R = __importStar(require("ramda")); var PresentationVerifier_1 = require("./PresentationVerifier"); /** * @param {Object} fields: a JSON properties fields object * @param {Object} paths: already calculated paths to be appended to * @param {String} pathPrepend: existing path for nested key in question * @returns {Array}: an array of path objects of type * { * {String} path * {String}: type * {String}: description * } */ var NESTED_PATH_DELIMITER = '.'; /** * for the given object, recursively return an array of all the paths inside the object, including nested paths * @param { [prop: string]: any } objToMatch: * @param {string} pathPrepend: a string that grows with recursion, i.e. 'credential', 'credential.email' * @returns {string[]} an array of paths i.e. ['credential', 'credential.email'] */ var getFlattenedPaths = function (objToMatch, pathPrepend) { if (pathPrepend === void 0) { pathPrepend = ''; } var localPathsObject = R.mapObjIndexed(function (value, key) { var currentPath = "" + pathPrepend + (pathPrepend ? NESTED_PATH_DELIMITER : '') + key; // recurse objects and arrays, otherwise just return the paths if (['object', 'array'].includes(typeof value)) { return getFlattenedPaths(value, currentPath); } else { return [currentPath]; } }, objToMatch); return Array.from(new Set(R.flatten(R.values(localPathsObject)))); // use a set to remove duplicates }; /** * takes an array of paths delimited with a '.' and checks that all the paths of the objToMatch and the objToCheck are equal * @param {string[]} flattenedPaths paths in an object e.g. ['identifier', 'credential.id'...] * @param { [prop: string]: any } objToMatch: object whose paths must all match objToCheck * @param { [prop: string]: any } objToCheck: object whose paths must all match objToMatch * @returns {Boolean}: whether all the paths match or not */ var matchAllObjectKeys = function (flattenedPaths, objToMatch) { return function (objToCheck) { return R.all(R.equals(true), flattenedPaths.map(R.split(NESTED_PATH_DELIMITER)).map(function (path) { return R.path(path, objToMatch) === R.path(path, objToCheck); })); }; }; /** * return the credential subject for credentials in the old or new schema (v3). * @param credential * @returns {any} object containing the claim values */ var getCredentialSubject = function (credential) { return (credential === null || credential === void 0 ? void 0 : credential.claim) || (credential === null || credential === void 0 ? void 0 : credential.credentialSubject); }; /** * Abstract all complexity about the Verifiable Credentials handling by providing utility methods * to access user verified data in a secure way unless the security behavior is explicit flexed. * * By Default the only check not performed is the blockchain anchor check that must be explicit enable * by providing a verification plugin that can handle the verification in a async way. */ var VerifiablePresentationManager = /** @class */ (function () { /** * @param options - Defines the global behavior and security of VerifiablePresentationManager * @param verifyAnchor - An async function that is able to verify the presentation anchor in a public Blockchain */ function VerifiablePresentationManager(options, verifyAnchor) { this.options = __assign({ skipAddVerify: false, skipGetVerify: false, allowGetUnverified: false, notThrow: false }, options); this.artifacts = { presentations: [], evidences: [] }; this.presentations = []; this.claims = []; this.status = { config: options, verifiedPresentations: 0, totalPresentations: 0, verifiedEvidences: 0, totalEvidences: 0 }; this.verifier = new PresentationVerifier_1.PresentationVerifier(verifyAnchor); } /** * Adds a set of Verifiable Presentations and Evidences to the manager control * * if neither `skipAddVerify` or `notThrow` are true, it throws an acception * once it process one invalid artifact. * * @param artifacts * */ // @ts-ignore VerifiablePresentationManager.prototype.addCredentialArtifacts = function (artifacts) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { this.aggregateCredentialArtifacts(artifacts); if (artifacts.presentations) { artifacts.presentations.forEach(function (presentation) { var presentationReference = _this.getPresentationReference(presentation); _this.presentations.push(presentationReference); var availableClaims = _this.getAvailableClaims(presentation.proof.leaves, presentationReference); _this.claims = _this.claims.concat(availableClaims); }); } if (!this.options.skipAddVerify) { return [2 /*return*/, this.verifyAllArtifacts()]; } this.status.totalPresentations = this.artifacts.presentations.length; this.status.totalEvidences = this.artifacts.evidences.length; return [2 /*return*/, this.status]; }); }); }; /** * List managed presentations returning in accordance with the config * * if `allowGetUnverified` is true, presentations that were not verified yet will be returned. * but known invalid presentations are never returned * */ VerifiablePresentationManager.prototype.listPresentations = function () { return __awaiter(this, void 0, void 0, function () { var verifiedPresentationRefs; return __generator(this, function (_a) { switch (_a.label) { case 0: verifiedPresentationRefs = []; if (!!this.options.skipGetVerify) return [3 /*break*/, 2]; return [4 /*yield*/, this.getVerifiedPresentationRefs()]; case 1: verifiedPresentationRefs = _a.sent(); _a.label = 2; case 2: return [2 /*return*/, (this.options.allowGetUnverified) ? this.presentations : verifiedPresentationRefs]; } }); }); }; ; /** * List managed claim returning in accordance with the config * * if `allowGetUnverified` is true, claim that were not verified yet will be returned. * but known invalid presentations are never returned * */ VerifiablePresentationManager.prototype.listClaims = function () { return __awaiter(this, void 0, void 0, function () { var claimsFromVerifiedPresentations, verifiedPresentationRefs_1; return __generator(this, function (_a) { switch (_a.label) { case 0: claimsFromVerifiedPresentations = []; if (!!this.options.skipGetVerify) return [3 /*break*/, 2]; return [4 /*yield*/, this.getVerifiedPresentationRefs()]; case 1: verifiedPresentationRefs_1 = _a.sent(); claimsFromVerifiedPresentations = R.filter(function (claim) { return verifiedPresentationRefs_1.includes(claim.credentialRef); }, this.claims); _a.label = 2; case 2: return [2 /*return*/, (this.options.allowGetUnverified) ? this.claims : claimsFromVerifiedPresentations]; } }); }); }; ; /** * List managed claim of a given Credential type returning in accordance with the config * * if `allowGetUnverified` is true, claim that were not verified yet will be returned. * but known invalid presentations are never returned * */ VerifiablePresentationManager.prototype.listPresentationClaims = function (presentationRef) { return __awaiter(this, void 0, void 0, function () { var verified, claims; return __generator(this, function (_a) { switch (_a.label) { case 0: verified = false; if (!!this.options.skipGetVerify) return [3 /*break*/, 2]; return [4 /*yield*/, this.verifyPresentation(presentationRef)]; case 1: verified = _a.sent(); _a.label = 2; case 2: claims = R.filter(R.propEq('credentialRef', presentationRef), this.claims); return [2 /*return*/, (this.options.allowGetUnverified || verified) ? claims : []]; } }); }); }; ; /** * Search for a valid claim that matches the criterias. * if `allowGetUnverified` is true the search also include claim not verified yet. * the search never includes known invalid claims */ VerifiablePresentationManager.prototype.findClaims = function (criteria) { return __awaiter(this, void 0, void 0, function () { var flattenedPaths, claims, verifiedPresentations, presentationRefs, _i, presentationRefs_1, presentationRef, verified; return __generator(this, function (_a) { switch (_a.label) { case 0: flattenedPaths = getFlattenedPaths(criteria); claims = R.filter(matchAllObjectKeys(flattenedPaths, criteria), this.claims); verifiedPresentations = []; if (!!this.options.skipGetVerify) return [3 /*break*/, 4]; presentationRefs = claims.map(function (claim) { return claim.credentialRef; }); _i = 0, presentationRefs_1 = presentationRefs; _a.label = 1; case 1: if (!(_i < presentationRefs_1.length)) return [3 /*break*/, 4]; presentationRef = presentationRefs_1[_i]; return [4 /*yield*/, this.verifyPresentation(presentationRef)]; case 2: verified = _a.sent(); if (verified) { verifiedPresentations.push(presentationRef); } _a.label = 3; case 3: _i++; return [3 /*break*/, 1]; case 4: return [2 /*return*/, (this.options.allowGetUnverified) ? claims : R.filter(function (claim) { return verifiedPresentations.includes(claim.credentialRef); }, claims)]; } }); }); }; /** * Get a mapping from key to a claim search criteria and resolve the claim search criterias, * returning a mapping from the same keys to the relative claim value. * if `allowGetUnverified` is true, then the search also includes claims not verified yet. * if no claim matches a claim criteria, the value for the relative key will be null. */ VerifiablePresentationManager.prototype.mapClaimValues = function (claimCriteriaMap, flatten) { return __awaiter(this, void 0, void 0, function () { var claimMappedValues, _i, _a, key, criteria, claims, _b, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: claimMappedValues = {}; _i = 0, _a = Object.keys(claimCriteriaMap); _e.label = 1; case 1: if (!(_i < _a.length)) return [3 /*break*/, 7]; key = _a[_i]; criteria = claimCriteriaMap[key]; return [4 /*yield*/, this.findClaims(criteria)]; case 2: claims = _e.sent(); _b = claimMappedValues; _c = key; if (!claims.length) return [3 /*break*/, 4]; return [4 /*yield*/, this.getClaimValue(claims[0])]; case 3: _d = _e.sent(); return [3 /*break*/, 5]; case 4: _d = null; _e.label = 5; case 5: _b[_c] = _d; _e.label = 6; case 6: _i++; return [3 /*break*/, 1]; case 7: if (flatten) { return [2 /*return*/, R.keys(claimMappedValues).map(function (key) { return ({ name: key, value: claimMappedValues[key] }); })]; } return [2 /*return*/, claimMappedValues]; } }); }); }; /** * return the STRING value of a valid avaliable claim. * if `allowGetUnverified` is true it returns unverified values. * if `notThrow` is true return null for known invalid claims */ VerifiablePresentationManager.prototype.getClaimValue = function (availableClaim) { return __awaiter(this, void 0, void 0, function () { var presentation, credentialSubject, verified; return __generator(this, function (_a) { switch (_a.label) { case 0: presentation = this.getClaimPresentation(availableClaim); credentialSubject = getCredentialSubject(presentation); if (!presentation || !credentialSubject) { return [2 /*return*/, null]; } verified = false; if (!!this.options.skipGetVerify) return [3 /*break*/, 2]; return [4 /*yield*/, this.verifyPresentation(availableClaim.credentialRef)]; case 1: verified = _a.sent(); _a.label = 2; case 2: ; if (!verified && !this.options.allowGetUnverified) { return [2 /*return*/, null]; } return [2 /*return*/, R.path(availableClaim.claimPath.split('.'), credentialSubject)]; } }); }); }; /** * List managed evidences * if `allowGetUnverified` is true it return unverified values. */ VerifiablePresentationManager.prototype.listEvidences = function () { return __awaiter(this, void 0, void 0, function () { var verifiedEvidences, verifiedPresentations; return __generator(this, function (_a) { switch (_a.label) { case 0: verifiedEvidences = []; if (!!this.options.skipGetVerify) return [3 /*break*/, 2]; return [4 /*yield*/, this.verifyPresentations(true)]; case 1: verifiedPresentations = _a.sent(); verifiedEvidences = this.verifyEvidences(verifiedPresentations); _a.label = 2; case 2: return [2 /*return*/, (this.options.allowGetUnverified) ? this.artifacts.evidences : verifiedEvidences]; } }); }); }; /** * Verify all artifacts and return a status of all presentations and evidences * * if neither `skipAddVerify` or `notThrow` are true, it throws an acception * once it process one invalid artifact. */ VerifiablePresentationManager.prototype.verifyAllArtifacts = function () { return __awaiter(this, void 0, void 0, function () { var verifiedPresentations, verifiedEvidences; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.verifyPresentations()]; case 1: verifiedPresentations = _a.sent(); verifiedEvidences = this.verifyEvidences(verifiedPresentations); this.status = { config: this.options, verifiedPresentations: verifiedPresentations.length, totalPresentations: this.artifacts.presentations.length, verifiedEvidences: verifiedEvidences.length, totalEvidences: this.artifacts.evidences.length, }; return [2 /*return*/, this.status]; } }); }); }; /** * Verify if a presentation was GRANTED for a specific DSR * * Verify if the presentation was shared with user consent and signatures * * @param presentationRef the managed presentation to verify * @param originalRequestDSR the original Dynamic Scope Request that receive the presentation as result */ VerifiablePresentationManager.prototype.wasGrantedForDSR = function (presentationRef, originalRequestDSR) { return __awaiter(this, void 0, void 0, function () { var dsr, requesterId, requestId, presentation; return __generator(this, function (_a) { dsr = JSON.parse(originalRequestDSR); requesterId = R.path('payload.requesterInfo.requesterId'.split('.'), dsr); requestId = R.path('payload.id'.split('.'), dsr); if (R.isEmpty(requesterId) || R.isEmpty(requestId)) { return [2 /*return*/, false]; } presentation = this.getPresentation(presentationRef); return [2 /*return*/, this.verifier.verifyGrant(presentation, requesterId, requestId)]; }); }); }; /** * Return true if all artifacts are verified, otherwise return false * * if neither `skipGetVerify` or `notThrow` are true, it throws an acception */ VerifiablePresentationManager.prototype.isAllArtifactsVerified = function () { return __awaiter(this, void 0, void 0, function () { var status, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!this.options.skipGetVerify) return [3 /*break*/, 1]; _a = this.status; return [3 /*break*/, 3]; case 1: return [4 /*yield*/, this.verifyAllArtifacts()]; case 2: _a = _b.sent(); _b.label = 3; case 3: status = _a; return [2 /*return*/, (status.totalPresentations === status.verifiedPresentations) && (status.totalEvidences === status.verifiedEvidences)]; } }); }); }; /** * Remove the invalid artifacts and return a status of the resultant artifacts */ VerifiablePresentationManager.prototype.purgeInvalidArtifacts = function () { return __awaiter(this, void 0, void 0, function () { var verifiedPresentations, verifiedIds, verifiedEvidences; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.verifyPresentations(true)]; case 1: verifiedPresentations = _a.sent(); verifiedIds = R.map(function (presentation) { return presentation.id; }, verifiedPresentations); verifiedEvidences = this.verifyEvidences(verifiedPresentations, true); this.artifacts = { presentations: verifiedPresentations, evidences: verifiedEvidences }; this.presentations = R.reject(function (presentationRef) { return !verifiedIds.includes(presentationRef.uid); }, this.presentations); this.claims = R.reject(function (claim) { return !verifiedIds.includes(claim.credentialRef.uid); }, this.claims); this.status.totalPresentations = this.artifacts.presentations.length; this.status.totalEvidences = this.artifacts.evidences.length; return [2 /*return*/, this.status]; } }); }); }; /* * Private mthods */ VerifiablePresentationManager.prototype.getPresentation = function (presentationRef) { return R.find(function (presentation) { return (presentation.id === presentationRef.uid && presentation.identifier === presentationRef.identifier); }, this.artifacts.presentations); }; VerifiablePresentationManager.prototype.getClaimPresentation = function (availableClaim) { return R.find(function (presentation) { return (presentation.id === availableClaim.credentialRef.uid); }, this.artifacts.presentations); }; VerifiablePresentationManager.prototype.findEvidencePresentation = function (evidence) { return R.find(function (presentation) { var credentialSubject = getCredentialSubject(presentation); var presentationClaims = JSON.stringify(credentialSubject); return presentationClaims.includes(evidence.sha256); }, this.artifacts.presentations); }; VerifiablePresentationManager.prototype.aggregateCredentialArtifacts = function (artifacts) { if (this.artifacts.presentations && artifacts.presentations) { this.artifacts.presentations = this.artifacts.presentations.concat(artifacts.presentations); } if (this.artifacts.evidences && artifacts.evidences) { this.artifacts.evidences = this.artifacts.evidences.concat(artifacts.evidences); } }; VerifiablePresentationManager.prototype.getPresentationReference = function (credential) { return { identifier: credential.identifier, uid: credential.id }; }; VerifiablePresentationManager.prototype.getAvailableClaims = function (claims, presentation) { return claims.map(function (claim) { return ({ identifier: claim.identifier, credentialRef: presentation, claimPath: claim.claimPath }); }); }; VerifiablePresentationManager.prototype.verifyPresentation = function (presentationRef) { return __awaiter(this, void 0, void 0, function () { var credential, verified; return __generator(this, function (_a) { switch (_a.label) { case 0: credential = this.getPresentation(presentationRef); return [4 /*yield*/, this.verifier.cryptographicallySecureVerify(credential)]; case 1: verified = _a.sent(); if (!this.options.notThrow && !verified) { throw new Error("Unverified Presentation: " + credential.id); } return [2 /*return*/, verified]; } }); }); }; VerifiablePresentationManager.prototype.verifyPresentations = function (notThrow) { if (notThrow === void 0) { notThrow = this.options.notThrow; } return __awaiter(this, void 0, void 0, function () { var verifiedPresentations, _i, _a, presentation, verified, unverifiedPresentations, unverifiedIds; return __generator(this, function (_b) { switch (_b.label) { case 0: verifiedPresentations = []; _i = 0, _a = this.artifacts.presentations; _b.label = 1; case 1: if (!(_i < _a.length)) return [3 /*break*/, 4]; presentation = _a[_i]; return [4 /*yield*/, this.verifier.cryptographicallySecureVerify(presentation)]; case 2: verified = _b.sent(); if (verified) { verifiedPresentations.push(presentation); } _b.label = 3; case 3: _i++; return [3 /*break*/, 1]; case 4: if (!notThrow) { unverifiedPresentations = R.difference(this.artifacts.presentations, verifiedPresentations); if (!R.isEmpty(unverifiedPresentations)) { unverifiedIds = R.map(function (presentation) { return presentation.id; }, unverifiedPresentations); throw new Error("Unverified Presentations: " + R.join(unverifiedIds)); } } return [2 /*return*/, verifiedPresentations]; } }); }); }; VerifiablePresentationManager.prototype.verifyEvidence = function (evidence, verifiedPresentations) { // check if there is a valid presentation referencing the evidence var presentation = this.findEvidencePresentation(evidence); if (!presentation || !R.find(R.propEq('id', presentation.id), verifiedPresentations)) { return false; } // check if the base64 data hash matches the sha256 value var dataPrefix = /^data:.*;base64,/; var base64Data = R.replace(dataPrefix, '', evidence.base64Encoded); // remove prefix try { var sha256BitArray = sjcl.hash.sha256.hash(sjcl.codec.base64.toBits(base64Data)); var calculatedSha256 = sjcl.codec.hex.fromBits(sha256BitArray); return (calculatedSha256 === evidence.sha256); } catch (e) { // default to false on any errors // e.g. base64 encoding exception } return false; }; VerifiablePresentationManager.prototype.verifyEvidences = function (verifiedPresentations, notThrow) { var _this = this; if (notThrow === void 0) { notThrow = this.options.notThrow; } var verifiedEvidences = R.filter(function (evidence) { return (_this.verifyEvidence(evidence, verifiedPresentations)); }, this.artifacts.evidences); if (!notThrow) { var unverifiedEvidences = R.difference(this.artifacts.evidences, verifiedEvidences); if (!R.isEmpty(unverifiedEvidences)) { var unverifiedIds = R.map(function (evidence) { return evidence.content; }, unverifiedEvidences); throw new Error("Unverified Evidences: " + R.join(unverifiedIds)); } } return verifiedEvidences; }; VerifiablePresentationManager.prototype.getVerifiedPresentationRefs = function () { return __awaiter(this, void 0, void 0, function () { var verifiedPresentations, verifiedIds; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.verifyPresentations()]; case 1: verifiedPresentations = _a.sent(); verifiedIds = R.map(function (presentation) { return presentation.id; }, verifiedPresentations); return [2 /*return*/, R.filter(function (presentation) { return verifiedIds.includes(presentation.uid); }, this.presentations)]; } }); }); }; return VerifiablePresentationManager; }()); exports.VerifiablePresentationManager = VerifiablePresentationManager;