UNPKG

anon-identity

Version:

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

552 lines 21.1 kB
"use strict"; /** * Secure Credential Management for MCP * * Handles secure storage, rotation, and access control for LLM provider credentials */ 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.CredentialManager = void 0; const events_1 = require("events"); const crypto = __importStar(require("crypto")); const types_1 = require("../types"); // Using MemoryStorageProvider directly for credential storage const memory_storage_provider_1 = require("../../storage/providers/memory-storage-provider"); /** * Secure Credential Manager */ class CredentialManager extends events_1.EventEmitter { constructor(config, storageProvider) { super(); this.config = config; this.credentials = new Map(); this.encryptionKey = null; this.encryptionConfig = { algorithm: 'aes-256-gcm', keyDerivationFunction: 'pbkdf2', iterations: 100000, saltLength: 32, ivLength: 16, tagLength: 16 }; this.rotationTimers = new Map(); this.validationCache = new Map(); this.storageProvider = storageProvider || new memory_storage_provider_1.MemoryStorageProvider(); this.initializeEncryption(); this.loadCredentials(); } /** * Initialize encryption */ async initializeEncryption() { if (!this.config.encryption) { return; } // In production, this master key should come from a secure key management service // For now, we'll derive it from a passphrase const passphrase = process.env.MCP_CREDENTIAL_PASSPHRASE || 'default-passphrase-change-me'; const salt = crypto.randomBytes(this.encryptionConfig.saltLength); this.encryptionKey = crypto.pbkdf2Sync(passphrase, salt, this.encryptionConfig.iterations, 32, 'sha256'); } /** * Load credentials from storage */ async loadCredentials() { try { // Use internal storage for credentials const storedCredentials = this.storageProvider._storage?.get('mcp:credentials'); if (storedCredentials) { const credentials = JSON.parse(storedCredentials); for (const cred of credentials) { // Decrypt if necessary if (cred.encryptedValue && this.config.encryption) { cred.value = await this.decrypt(cred.encryptedValue); } // Convert dates cred.metadata.createdAt = new Date(cred.metadata.createdAt); cred.metadata.updatedAt = new Date(cred.metadata.updatedAt); if (cred.metadata.expiresAt) { cred.metadata.expiresAt = new Date(cred.metadata.expiresAt); } if (cred.metadata.rotatedAt) { cred.metadata.rotatedAt = new Date(cred.metadata.rotatedAt); } if (cred.metadata.lastUsedAt) { cred.metadata.lastUsedAt = new Date(cred.metadata.lastUsedAt); } this.credentials.set(cred.id, cred); // Setup rotation if configured if (this.config.rotation?.enabled) { this.scheduleRotation(cred); } } } } catch (error) { this.emit('error', new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_CONFIG, message: `Failed to load credentials: ${error.message}`, timestamp: new Date(), retryable: false })); } } /** * Save credentials to storage */ async saveCredentials() { const credentialsToStore = Array.from(this.credentials.values()).map(cred => { const stored = { ...cred }; // Encrypt if necessary if (this.config.encryption && this.encryptionKey) { stored.encryptedValue = this.encrypt(cred.value); delete stored.value; // Don't store plaintext } return stored; }); // Use internal storage for credentials this.storageProvider._storage = this.storageProvider._storage || new Map(); this.storageProvider._storage.set('mcp:credentials', JSON.stringify(credentialsToStore)); } /** * Add credential */ async addCredential(providerId, type, value, metadata) { const id = `cred-${providerId}-${Date.now()}`; const credential = { id, providerId, type, value, metadata: { createdAt: new Date(), updatedAt: new Date(), usageCount: 0, ...metadata } }; // Validate if configured if (this.config.validation?.validateOnLoad) { const validation = await this.validateCredential(credential); credential.validation = { validated: true, validatedAt: new Date(), validationError: validation.error }; if (!validation.valid) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_CREDENTIALS, message: `Invalid credential: ${validation.error}`, timestamp: new Date(), provider: providerId, retryable: false }); } } this.credentials.set(id, credential); await this.saveCredentials(); // Schedule rotation if configured if (this.config.rotation?.enabled) { this.scheduleRotation(credential); } this.emit('credential_added', credential); return credential; } /** * Get credential for provider */ async getCredential(providerId) { // Find most recent valid credential for provider const providerCredentials = Array.from(this.credentials.values()) .filter(cred => cred.providerId === providerId) .sort((a, b) => b.metadata.createdAt.getTime() - a.metadata.createdAt.getTime()); for (const credential of providerCredentials) { // Check expiration if (credential.metadata.expiresAt && credential.metadata.expiresAt < new Date()) { continue; } // Validate on use if configured if (this.config.validation?.validateOnUse) { const validation = await this.validateCredential(credential); if (!validation.valid) { credential.validation = { validated: true, validatedAt: new Date(), validationError: validation.error }; await this.saveCredentials(); continue; } } // Update usage statistics credential.metadata.lastUsedAt = new Date(); credential.metadata.usageCount++; await this.saveCredentials(); return credential; } return null; } /** * Update credential */ async updateCredential(credentialId, updates) { const credential = this.credentials.get(credentialId); if (!credential) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_CREDENTIALS, message: `Credential not found: ${credentialId}`, timestamp: new Date(), retryable: false }); } if (updates.value) { credential.value = updates.value; credential.metadata.rotatedAt = new Date(); } if (updates.metadata) { credential.metadata = { ...credential.metadata, ...updates.metadata }; } credential.metadata.updatedAt = new Date(); // Re-validate if value changed if (updates.value && this.config.validation?.validateOnLoad) { const validation = await this.validateCredential(credential); credential.validation = { validated: true, validatedAt: new Date(), validationError: validation.error }; } await this.saveCredentials(); this.emit('credential_updated', credential); return credential; } /** * Delete credential */ async deleteCredential(credentialId) { const credential = this.credentials.get(credentialId); if (!credential) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_CREDENTIALS, message: `Credential not found: ${credentialId}`, timestamp: new Date(), retryable: false }); } // Cancel rotation timer const timer = this.rotationTimers.get(credentialId); if (timer) { clearTimeout(timer); this.rotationTimers.delete(credentialId); } this.credentials.delete(credentialId); this.validationCache.delete(credentialId); await this.saveCredentials(); this.emit('credential_deleted', credential); } /** * Rotate credential */ async rotateCredential(credentialId, newValue) { const oldCredential = this.credentials.get(credentialId); if (!oldCredential) { return { rotated: false, oldCredentialId: credentialId, error: 'Credential not found' }; } try { // Create new credential const newCredential = await this.addCredential(oldCredential.providerId, oldCredential.type, newValue, { ...oldCredential.metadata, description: `Rotated from ${credentialId}` }); // Mark old credential as rotated oldCredential.metadata.expiresAt = new Date(); await this.saveCredentials(); // Delete old credential after retention period if (this.config.rotation?.retentionCount) { // Keep only the configured number of old credentials const providerCredentials = Array.from(this.credentials.values()) .filter(cred => cred.providerId === oldCredential.providerId) .sort((a, b) => b.metadata.createdAt.getTime() - a.metadata.createdAt.getTime()); if (providerCredentials.length > this.config.rotation.retentionCount) { const toDelete = providerCredentials.slice(this.config.rotation.retentionCount); for (const cred of toDelete) { await this.deleteCredential(cred.id); } } } this.emit('credential_rotated', { oldCredential, newCredential }); return { rotated: true, oldCredentialId: credentialId, newCredentialId: newCredential.id }; } catch (error) { return { rotated: false, oldCredentialId: credentialId, error: error.message }; } } /** * Validate credential */ async validateCredential(credential) { // Check cache first if (this.config.validation?.cacheValidation) { const cached = this.validationCache.get(credential.id); if (cached) { return cached; } } const result = { valid: true }; try { // Basic validation if (!credential.value || credential.value.trim().length === 0) { result.valid = false; result.error = 'Credential value is empty'; } // Type-specific validation switch (credential.type) { case 'api_key': if (credential.value.length < 32) { result.valid = false; result.error = 'API key too short'; } break; case 'oauth_token': // Check token format if (!credential.value.startsWith('Bearer ') && !credential.value.includes('.')) { result.valid = false; result.error = 'Invalid OAuth token format'; } break; case 'certificate': // Basic certificate validation if (!credential.value.includes('BEGIN CERTIFICATE')) { result.valid = false; result.error = 'Invalid certificate format'; } break; } // Cache result if (this.config.validation?.cacheValidation) { this.validationCache.set(credential.id, result); // Clear cache after timeout setTimeout(() => { this.validationCache.delete(credential.id); }, this.config.validation?.validationTimeout || 300000); // 5 minutes default } } catch (error) { result.valid = false; result.error = error.message; } return result; } /** * Schedule credential rotation */ scheduleRotation(credential) { if (!this.config.rotation?.enabled || !this.config.rotation.interval) { return; } // Clear existing timer const existingTimer = this.rotationTimers.get(credential.id); if (existingTimer) { clearTimeout(existingTimer); } // Calculate next rotation time const lastRotation = credential.metadata.rotatedAt || credential.metadata.createdAt; const nextRotation = new Date(lastRotation.getTime() + this.config.rotation.interval); const timeUntilRotation = Math.max(0, nextRotation.getTime() - Date.now()); // Notify before rotation if configured if (this.config.rotation.notifyBefore) { const notifyTime = timeUntilRotation - this.config.rotation.notifyBefore; if (notifyTime > 0) { setTimeout(() => { this.emit('rotation_due', { credential, dueIn: this.config.rotation.notifyBefore }); }, notifyTime); } } // Schedule rotation const timer = setTimeout(() => { this.emit('rotation_required', credential); this.rotationTimers.delete(credential.id); }, timeUntilRotation); this.rotationTimers.set(credential.id, timer); } /** * Encrypt value */ encrypt(value) { if (!this.encryptionKey) { throw new Error('Encryption key not initialized'); } const iv = crypto.randomBytes(this.encryptionConfig.ivLength); const cipher = crypto.createCipheriv(this.encryptionConfig.algorithm, this.encryptionKey, iv); let encrypted = cipher.update(value, 'utf8', 'base64'); encrypted += cipher.final('base64'); const tag = cipher.getAuthTag(); // Combine IV, tag, and encrypted data const combined = Buffer.concat([ iv, tag, Buffer.from(encrypted, 'base64') ]); return combined.toString('base64'); } /** * Decrypt value */ decrypt(encryptedValue) { if (!this.encryptionKey) { throw new Error('Encryption key not initialized'); } const combined = Buffer.from(encryptedValue, 'base64'); // Extract IV, tag, and encrypted data const iv = combined.slice(0, this.encryptionConfig.ivLength); const tag = combined.slice(this.encryptionConfig.ivLength, this.encryptionConfig.ivLength + this.encryptionConfig.tagLength); const encrypted = combined.slice(this.encryptionConfig.ivLength + this.encryptionConfig.tagLength); const decipher = crypto.createDecipheriv(this.encryptionConfig.algorithm, this.encryptionKey, iv); decipher.setAuthTag(tag); let decrypted = decipher.update(encrypted.toString('base64'), 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } /** * Get credential statistics */ getStatistics() { const stats = { total: this.credentials.size, byProvider: {}, byType: {}, expired: 0, rotating: 0, validated: 0, failed: 0 }; const now = new Date(); for (const credential of this.credentials.values()) { // Count by provider stats.byProvider[credential.providerId] = (stats.byProvider[credential.providerId] || 0) + 1; // Count by type stats.byType[credential.type] = (stats.byType[credential.type] || 0) + 1; // Count expired if (credential.metadata.expiresAt && credential.metadata.expiresAt < now) { stats.expired++; } // Count rotating if (this.rotationTimers.has(credential.id)) { stats.rotating++; } // Count validated if (credential.validation?.validated) { stats.validated++; if (credential.validation.validationError) { stats.failed++; } } } return stats; } /** * Export credentials (for backup) */ async exportCredentials(includeValues = false) { const credentials = Array.from(this.credentials.values()).map(cred => { const exported = { id: cred.id, providerId: cred.providerId, type: cred.type, metadata: cred.metadata, validation: cred.validation }; if (includeValues) { exported.value = cred.value; } return exported; }); return JSON.stringify(credentials, null, 2); } /** * Import credentials */ async importCredentials(data, overwrite = false) { const credentials = JSON.parse(data); let imported = 0; for (const cred of credentials) { if (!overwrite && this.credentials.has(cred.id)) { continue; } await this.addCredential(cred.providerId, cred.type, cred.value, cred.metadata); imported++; } return imported; } /** * Shutdown credential manager */ async shutdown() { // Clear all rotation timers for (const timer of this.rotationTimers.values()) { clearTimeout(timer); } this.rotationTimers.clear(); // Clear caches this.validationCache.clear(); // Save final state await this.saveCredentials(); // Clear sensitive data this.credentials.clear(); this.encryptionKey = null; this.removeAllListeners(); } } exports.CredentialManager = CredentialManager; exports.default = CredentialManager; //# sourceMappingURL=credential-manager.js.map