UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

417 lines (416 loc) 15.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CoreVerifier = void 0; exports.verifyWorker = verifyWorker; exports.verifyExpress = verifyExpress; const verifier_1 = require("@kya-os/contracts/verifier"); const merkle_verifier_1 = require("../storage/merkle-verifier"); const delegation_1 = require("../storage/delegation"); const mcp_i_core_1 = require("@kya-os/mcp-i-core"); /** * Core verifier implementation */ class CoreVerifier { config; receiptVerifier; delegationManager; cryptoService; constructor(config = {}) { this.config = config; this.receiptVerifier = (0, merkle_verifier_1.createReceiptVerifier)({ enabled: config.receiptVerification !== false, ktaBaseUrl: config.ktaBaseUrl, allowMockData: config.allowMockData, }); this.delegationManager = (0, delegation_1.createDelegationManager)({ ktaBaseURL: config.ktaBaseUrl || "https://knowthat.ai", }); // Initialize CryptoService if cryptoProvider is provided if (config.cryptoProvider) { this.cryptoService = new mcp_i_core_1.CryptoService(config.cryptoProvider); } } /** * Verify proof with optional receipt checking */ async verify(context) { try { // 1. Verify proof signature (placeholder - would use actual crypto verification) const signatureValid = await this.verifySignature(context.proof, context.detachedProof); if (!signatureValid) { return { success: false, error: { code: "XMCP_I_EBADPROOF", message: "Invalid proof signature", httpStatus: 403, }, }; } // 2. Check delegation if present if (context.proof.delegationRef && this.config.delegationChecking !== false) { const delegationValid = await this.verifyDelegation(context.proof.delegationRef); if (!delegationValid) { return { success: false, error: { code: "XMCP_I_EBADPROOF", message: "Delegation revoked or invalid", httpStatus: 403, }, }; } } // 3. Verify receipt if present and policy requires it if (context.receipt && this.shouldVerifyReceipt()) { const receiptResult = await this.receiptVerifier.verify(context.receipt); if (!receiptResult.valid) { return { success: false, error: { code: "XMCP_I_ERECEIPT", message: `Receipt verification failed: ${receiptResult.error}`, httpStatus: 403, }, }; } } // 4. Generate trusted headers const headers = this.generateHeaders(context.proof); return { success: true, headers, }; } catch (error) { return { success: false, error: { code: "XMCP_I_EVERIFY", message: error instanceof Error ? error.message : "Verification failed", httpStatus: 500, }, }; } } /** * Verify proof signature * * Note: Full signature verification requires DetachedProof with JWS. * If only ProofMeta is available, performs structure validation only. * To enable full verification, provide detachedProof in VerifierContext. */ async verifySignature(proof, detachedProof) { // Basic structure validation const hasRequiredFields = !!(proof.did && proof.kid && proof.ts && proof.nonce && proof.audience && proof.sessionId && proof.requestHash && proof.responseHash); if (!hasRequiredFields) { return false; } // If we have full DetachedProof with JWS and CryptoService, verify signature if (detachedProof?.jws && this.cryptoService) { try { // Fetch public key from DID document // Note: This is a simplified implementation - production should use proper DID resolver const publicKeyJwk = await this.fetchPublicKeyFromDID(proof.did, proof.kid); if (!publicKeyJwk) { console.warn("[CoreVerifier] Could not resolve public key from DID, skipping signature verification"); return true; // Fall back to structure validation only } // Verify JWS signature return await this.cryptoService.verifyJWS(detachedProof.jws, publicKeyJwk, { expectedKid: proof.kid, alg: "EdDSA", }); } catch (error) { console.error("[CoreVerifier] Signature verification error:", error); return false; } } // Fallback: structure validation only (when JWS not available) return true; } /** * Fetch public key from DID document * * Note: This is a simplified implementation. Production code should use * a proper DID resolver that supports multiple DID methods (did:key, did:web, etc.) */ async fetchPublicKeyFromDID(did, kid) { // For now, only support did:web resolution // TODO: Add support for did:key and other DID methods if (!did.startsWith("did:web:")) { console.warn(`[CoreVerifier] Unsupported DID method: ${did}`); return null; } try { const domain = did.replace("did:web:", "").replace(/:/g, "/"); const didDocUrl = `https://${domain}/.well-known/did.json`; const response = await fetch(didDocUrl); if (!response.ok) { console.warn(`[CoreVerifier] Failed to fetch DID document: ${response.status}`); return null; } const didDoc = await response.json(); // Find verification method const verificationMethod = didDoc.verificationMethod?.find((vm) => vm.id === `#${kid}` || vm.id === `${did}#${kid}` || vm.id === kid); if (!verificationMethod?.publicKeyJwk) { console.warn(`[CoreVerifier] Verification method ${kid} not found in DID document`); return null; } // Validate JWK format const jwk = verificationMethod.publicKeyJwk; if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519") { console.warn(`[CoreVerifier] Invalid JWK format: expected Ed25519, got ${jwk.kty}/${jwk.crv}`); return null; } return jwk; } catch (error) { console.error("[CoreVerifier] DID resolution error:", error); return null; } } /** * Verify delegation status */ async verifyDelegation(delegationRef) { try { return await this.delegationManager.isActive(delegationRef); } catch (error) { console.warn("Delegation verification failed:", error); return false; } } /** * Check if receipt verification should be performed */ shouldVerifyReceipt() { const policy = this.config.receiptPolicy || "optional"; return (policy === "required" || (policy === "optional" && this.config.receiptVerification !== false)); } /** * Generate trusted headers for successful verification */ generateHeaders(proof) { const headers = { [verifier_1.AGENT_HEADERS.DID]: proof.did, [verifier_1.AGENT_HEADERS.KEY_ID]: proof.kid, [verifier_1.AGENT_HEADERS.SESSION]: proof.sessionId, [verifier_1.AGENT_HEADERS.CONFIDENCE]: "verified", [verifier_1.AGENT_HEADERS.VERIFIED_AT]: Math.floor(Date.now() / 1000).toString(), }; if (proof.scopeId) { headers[verifier_1.AGENT_HEADERS.SCOPES] = proof.scopeId; } if (proof.delegationRef) { headers[verifier_1.AGENT_HEADERS.DELEGATION_REF] = proof.delegationRef; } // Add registry URL for traceability const ktaUrl = this.config.ktaBaseUrl || "https://knowthat.ai"; headers[verifier_1.AGENT_HEADERS.REGISTRY] = `${ktaUrl}/agents/${encodeURIComponent(proof.did)}`; return headers; } } exports.CoreVerifier = CoreVerifier; /** * Cloudflare Worker verifier */ async function verifyWorker(request, config) { try { // Extract proof from request headers or body const proof = await parseProofFromHeaders(request); if (!proof) { return { success: false, error: { code: "XMCP_I_ENOIDENTITY", message: "No proof found in request", httpStatus: 401, }, }; } // Extract receipt if present const receipt = await parseReceiptFromHeaders(request); const verifier = new CoreVerifier(config); return await verifier.verify({ proof, receipt: receipt || undefined }); } catch (error) { return { success: false, error: { code: "XMCP_I_EVERIFY", message: error instanceof Error ? error.message : "Verification failed", httpStatus: 500, }, }; } } /** * Express verifier middleware */ function verifyExpress(config) { return async (req, res, next) => { try { // Extract proof from request const proof = await parseProofFromExpressHeaders(req); if (!proof) { return res.status(401).json({ code: "XMCP_I_ENOIDENTITY", message: "No proof found in request", }); } // Extract receipt if present const receipt = await parseReceiptFromExpressHeaders(req); const verifier = new CoreVerifier(config); const result = await verifier.verify({ proof, receipt: receipt || undefined, }); if (!result.success) { return res.status(result.error.httpStatus).json({ code: result.error.code, message: result.error.message, details: result.error.details, }); } // Set headers and context if (result.headers) { Object.entries(result.headers).forEach(([key, value]) => { res.setHeader(key, value); }); // Set ctx.agent for MCP recipients req.ctx = req.ctx || {}; req.ctx.agent = { did: result.headers[verifier_1.AGENT_HEADERS.DID], kid: result.headers[verifier_1.AGENT_HEADERS.KEY_ID], session: result.headers[verifier_1.AGENT_HEADERS.SESSION], scopes: result.headers[verifier_1.AGENT_HEADERS.SCOPES]?.split(",") || [], delegationRef: result.headers[verifier_1.AGENT_HEADERS.DELEGATION_REF], confidence: result.headers[verifier_1.AGENT_HEADERS.CONFIDENCE], verifiedAt: parseInt(result.headers[verifier_1.AGENT_HEADERS.VERIFIED_AT]), }; } next(); } catch (error) { res.status(500).json({ code: "XMCP_I_EVERIFY", message: error instanceof Error ? error.message : "Verification failed", }); } }; } /** * Helper: Extract header value from different header types */ function getHeaderValue(headers, name) { if (headers instanceof Headers) { return headers.get(name); } const header = headers[name.toLowerCase()]; if (!header) return null; return Array.isArray(header) ? header[0] : header; } /** * Parse proof from request headers * * This function extracts ProofMeta from request headers. * ProofMeta type is defined in @kya-os/contracts/proof. * * @param request - Fetch API Request or Express Request * @returns ProofMeta (from @kya-os/contracts/proof) if found, null otherwise */ async function parseProofFromHeaders(request) { // TODO: Implement actual proof extraction from request // This would typically look for proof in headers or request body // Placeholder implementation // Handle both Fetch API Request and Node.js IncomingMessage const proofHeader = getHeaderValue(request.headers, "X-XMCP-I-Proof"); if (!proofHeader) { return null; } try { return JSON.parse(proofHeader); } catch { return null; } } /** * Parse receipt from request headers * * This function extracts Receipt from request headers. * Receipt type is defined in @kya-os/contracts/registry. * * @param request - Fetch API Request or Express Request * @returns Receipt (from @kya-os/contracts/registry) if found, null otherwise */ async function parseReceiptFromHeaders(request) { // TODO: Implement actual receipt extraction from request // This would typically look for receipt reference in headers // Handle both Fetch API Request and Node.js IncomingMessage const receiptRef = getHeaderValue(request.headers, "X-XMCP-I-Receipt-Ref"); if (!receiptRef) { return null; } // TODO: Fetch receipt by reference from storage return null; } /** * Parse proof from Express request headers * * This function extracts ProofMeta from Express request headers. * ProofMeta type is defined in @kya-os/contracts/proof. * * @param req - Express Request * @returns ProofMeta (from @kya-os/contracts/proof) if found, null otherwise */ async function parseProofFromExpressHeaders(req) { // TODO: Implement actual proof extraction from Express request // This would typically look for proof in headers or request body const proofHeader = req.headers["x-xmcp-i-proof"]; if (!proofHeader) { return null; } try { const headerValue = Array.isArray(proofHeader) ? proofHeader[0] : proofHeader; return JSON.parse(headerValue); } catch { return null; } } /** * Parse receipt from Express request headers * * This function extracts Receipt from Express request headers. * Receipt type is defined in @kya-os/contracts/registry. * * @param req - Express Request * @returns Receipt (from @kya-os/contracts/registry) if found, null otherwise */ async function parseReceiptFromExpressHeaders(req) { // TODO: Implement actual receipt extraction from Express request // This would typically look for receipt reference in headers const receiptRef = req.headers["x-xmcp-i-receipt-ref"]; if (!receiptRef) { return null; } // TODO: Fetch receipt by reference from storage return null; }