UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

327 lines 13.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.DelegationChainValidator = void 0; const did_1 = require("../core/did"); const ed = __importStar(require("@noble/ed25519")); class DelegationChainValidator { constructor(delegationManager, agentManager) { this.delegationManager = delegationManager; this.agentManager = agentManager; this.chainCache = new Map(); this.cacheTimeout = 5 * 60 * 1000; // 5 minutes } /** * Validates a complete delegation chain from root to target agent */ async validateDelegationChain(targetAgentDID, rootDID, serviceDID) { const errors = []; const warnings = []; // Check cache first const cacheKey = `${rootDID}:${targetAgentDID}:${serviceDID || 'all'}`; const cached = this.getCachedChain(cacheKey); if (cached) { return { valid: true, chain: cached, errors: [], warnings: [] }; } try { // Build the chain from target to root const chain = await this.buildDelegationChain(targetAgentDID, rootDID); if (!chain) { errors.push('Failed to build delegation chain'); return { valid: false, errors, warnings }; } // Validate each link in the chain for (let i = 0; i < chain.credentials.length; i++) { const credential = chain.credentials[i]; const validationResult = await this.validateChainLink(credential, i > 0 ? chain.credentials[i - 1] : undefined, serviceDID); if (!validationResult.valid) { errors.push(`Link ${i + 1}: ${validationResult.error}`); } if (validationResult.warning) { warnings.push(`Link ${i + 1}: ${validationResult.warning}`); } } // Validate overall chain properties const chainValidation = this.validateChainProperties(chain); errors.push(...chainValidation.errors); warnings.push(...chainValidation.warnings); const valid = errors.length === 0; if (valid) { this.cacheChain(cacheKey, chain); } return { valid, chain, errors, warnings }; } catch (error) { errors.push(`Chain validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); return { valid: false, errors, warnings }; } } /** * Builds a delegation chain from target agent to root */ async buildDelegationChain(targetAgentDID, rootDID) { const agents = []; const credentials = []; let currentDID = targetAgentDID; let maxDepth = 0; while (currentDID !== rootDID) { const agent = this.agentManager.getAgent(currentDID); if (!agent) { // If we can't find the agent, we might have reached a user DID if (agents.length > 0 && agents[agents.length - 1].parentDID === rootDID) { break; } return null; } agents.push(agent); maxDepth = Math.max(maxDepth, agent.maxDelegationDepth || 3); // Get delegation credential for this agent const agentCredentials = this.agentManager.getDelegationCredentials(currentDID); if (agentCredentials.length === 0) { return null; } // Find the most recent valid credential const validCredential = this.findValidCredential(agentCredentials); if (!validCredential) { return null; } credentials.push(validCredential); currentDID = agent.parentDID; // Prevent infinite loops if (agents.length > 10) { throw new Error('Delegation chain too deep or circular reference detected'); } } return { agents: agents.reverse(), credentials: credentials.reverse(), maxDepth, currentDepth: agents.length }; } /** * Validates a single link in the delegation chain */ async validateChainLink(credential, parentCredential, serviceDID) { // Basic credential validation if (!this.delegationManager.validateDelegation(credential)) { return { valid: false, error: 'Invalid or expired credential' }; } // Verify signature const signatureValid = await this.verifyCredentialSignature(credential); if (!signatureValid) { return { valid: false, error: 'Invalid credential signature' }; } // If this is not the root credential, validate against parent if (parentCredential) { // Check that parent can delegate if (!this.delegationManager.canAgentDelegate(parentCredential)) { return { valid: false, error: 'Parent cannot delegate' }; } // Validate scope inheritance if (serviceDID) { const parentScopes = this.delegationManager.extractScopes(parentCredential, serviceDID); const currentScopes = this.delegationManager.extractScopes(credential, serviceDID); const validation = this.delegationManager.validateAgentDelegation(parentCredential, currentScopes, serviceDID); if (!validation.valid) { return { valid: false, error: validation.reason }; } } // Check delegation depth consistency const parentDepth = parentCredential.credentialSubject.delegationDepth || 0; const currentDepth = credential.credentialSubject.delegationDepth || 0; if (currentDepth !== parentDepth + 1) { return { valid: true, warning: `Inconsistent delegation depth: expected ${parentDepth + 1}, got ${currentDepth}` }; } } return { valid: true }; } /** * Verifies the cryptographic signature of a credential */ async verifyCredentialSignature(credential) { try { if (!credential.proof?.jws) { return false; } // Extract the issuer's public key const issuerDID = credential.issuer; const issuerAgent = this.agentManager.getAgent(issuerDID); let publicKey; if (issuerAgent) { publicKey = issuerAgent.keyPair.publicKey; } else { // Try to extract public key from DID try { publicKey = did_1.DIDService.getPublicKeyFromDID(issuerDID); } catch { return false; } } // Remove proof for verification const { proof, ...credentialWithoutProof } = credential; const message = JSON.stringify(credentialWithoutProof, Object.keys(credentialWithoutProof).sort()); // Verify signature const signature = credential.proof.jws; const signatureBytes = typeof signature === 'string' ? Uint8Array.from(Buffer.from(signature, 'base64')) : signature; return await ed.verify(signatureBytes, new TextEncoder().encode(message), publicKey); } catch (error) { return false; } } /** * Validates overall chain properties */ validateChainProperties(chain) { const errors = []; const warnings = []; // Check maximum depth if (chain.currentDepth > chain.maxDepth) { errors.push(`Chain depth (${chain.currentDepth}) exceeds maximum allowed (${chain.maxDepth})`); } // Check for expired credentials const now = new Date(); chain.credentials.forEach((cred, index) => { const expirationDate = new Date(cred.expirationDate); const timeUntilExpiration = expirationDate.getTime() - now.getTime(); if (timeUntilExpiration < 24 * 60 * 60 * 1000) { // Less than 24 hours warnings.push(`Credential for agent ${index + 1} expires soon`); } }); // Check for scope degradation if (chain.credentials.length > 1) { for (let i = 1; i < chain.credentials.length; i++) { const parentScopes = chain.credentials[i - 1].credentialSubject.scopes; const currentScopes = chain.credentials[i].credentialSubject.scopes; if (currentScopes.length > parentScopes.length) { warnings.push(`Agent ${i + 1} has more scopes than parent - possible configuration issue`); } } } return { errors, warnings }; } /** * Finds the most recent valid credential from a list */ findValidCredential(credentials) { const now = new Date(); const validCredentials = credentials .filter(cred => { const expirationDate = new Date(cred.expirationDate); const validFrom = new Date(cred.credentialSubject.validFrom); return expirationDate > now && validFrom <= now; }) .sort((a, b) => { // Sort by issuance date, most recent first return new Date(b.issuanceDate).getTime() - new Date(a.issuanceDate).getTime(); }); return validCredentials[0] || null; } /** * Caches a validated chain */ cacheChain(key, chain) { const now = new Date(); const expiresAt = new Date(now.getTime() + this.cacheTimeout); // Find the earliest credential expiration const earliestExpiration = chain.credentials.reduce((earliest, cred) => { const expDate = new Date(cred.expirationDate); return expDate < earliest ? expDate : earliest; }, expiresAt); this.chainCache.set(key, { chain, validatedAt: now, expiresAt: earliestExpiration < expiresAt ? earliestExpiration : expiresAt }); } /** * Retrieves a cached chain if still valid */ getCachedChain(key) { const entry = this.chainCache.get(key); if (!entry) return null; const now = new Date(); if (now > entry.expiresAt) { this.chainCache.delete(key); return null; } return entry.chain; } /** * Clears expired entries from the cache */ clearExpiredCache() { const now = new Date(); for (const [key, entry] of this.chainCache.entries()) { if (now > entry.expiresAt) { this.chainCache.delete(key); } } } /** * Exports a delegation chain for visualization or debugging */ exportChain(chain) { return { depth: chain.currentDepth, maxDepth: chain.maxDepth, agents: chain.agents.map(agent => ({ did: agent.did, name: agent.name, delegationDepth: agent.delegationDepth, canDelegate: agent.canDelegate })), credentials: chain.credentials.map(cred => ({ id: cred.id, issuer: cred.issuer, subject: cred.credentialSubject.id, scopes: cred.credentialSubject.scopes, issuanceDate: cred.issuanceDate, expirationDate: cred.expirationDate })) }; } } exports.DelegationChainValidator = DelegationChainValidator; //# sourceMappingURL=delegation-chain-validator.js.map