UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

341 lines (340 loc) 11.4 kB
"use strict"; /** * Local verification utilities for offline testing */ Object.defineProperty(exports, "__esModule", { value: true }); exports.verifyProofLocally = verifyProofLocally; exports.verifyDIDDocumentLocally = verifyDIDDocumentLocally; exports.createMockProof = createMockProof; const crypto_1 = require("crypto"); const json_canonicalize_1 = require("json-canonicalize"); const test_1 = require("@kya-os/contracts/test"); const test_environment_1 = require("./test-environment"); const mock_identity_provider_1 = require("./mock-identity-provider"); /** * Canonicalize data using JCS (JSON Canonicalization Scheme) RFC 8785 */ function canonicalizeJSON(data) { return (0, json_canonicalize_1.canonicalize)(data); } /** * Generate SHA-256 hash with prefix */ function generateHash(data) { return `sha256:${(0, crypto_1.createHash)("sha256").update(data).digest("hex")}`; } /** * Verify Ed25519 signature locally (mock implementation for testing) */ function verifySignature(data, signature, publicKey) { (0, test_environment_1.ensureTestMode)(); try { // Mock signature verification for testing // In production, this would use proper Ed25519 verification const expectedSignature = (0, crypto_1.createHash)("sha256") .update(data + publicKey) .digest("base64"); const valid = signature === expectedSignature; return { valid, error: valid ? undefined : "Signature verification failed", }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : "Unknown signature verification error", }; } } /** * Verify proof structure and content */ function verifyProofStructure(proof) { (0, test_environment_1.ensureTestMode)(); const errors = []; // Check required fields const requiredFields = ["jws", "meta"]; const requiredMetaFields = [ "did", "kid", "ts", "nonce", "audience", "sessionId", "requestHash", "responseHash", ]; let structure = true; if (!proof || typeof proof !== "object") { structure = false; errors.push("Proof must be an object"); } else { for (const field of requiredFields) { if (!(field in proof)) { structure = false; errors.push(`Missing required field: ${field}`); } } if (proof.meta && typeof proof.meta === "object") { for (const field of requiredMetaFields) { if (!(field in proof.meta)) { structure = false; errors.push(`Missing required meta field: ${field}`); } } } else { structure = false; errors.push("Proof meta must be an object"); } } // Check timestamp validity (within reasonable bounds) let timestamps = true; if (proof?.meta?.ts) { const now = Date.now() / 1000; const proofTime = proof.meta.ts; const skew = Math.abs(now - proofTime); if (skew > 300) { // 5 minutes for testing timestamps = false; errors.push(`Timestamp too far from current time: ${skew}s`); } } else { timestamps = false; errors.push("Missing or invalid timestamp"); } // Check hash format let hashes = true; if (proof?.meta?.requestHash && !proof.meta.requestHash.startsWith("sha256:")) { hashes = false; errors.push("Request hash must start with 'sha256:'"); } if (proof?.meta?.responseHash && !proof.meta.responseHash.startsWith("sha256:")) { hashes = false; errors.push("Response hash must start with 'sha256:'"); } const valid = structure && timestamps && hashes; return { valid, structure, timestamps, hashes, error: errors.length > 0 ? errors.join("; ") : undefined, }; } /** * Verify session validity */ function verifySession(sessionId, timestamp) { (0, test_environment_1.ensureTestMode)(); if (!sessionId || typeof sessionId !== "string") { return { valid: false, expired: false, error: "Invalid session ID", }; } // For testing, assume sessions are valid for 30 minutes const now = Date.now() / 1000; const sessionAge = now - timestamp; const expired = sessionAge > 1800; // 30 minutes return { valid: !expired, expired, error: expired ? "Session expired" : undefined, }; } /** * Perform local verification of a proof without KTA calls */ async function verifyProofLocally(proof, request, response) { (0, test_environment_1.ensureTestMode)(); try { const errors = []; const warnings = []; // Verify proof structure const proofCheck = verifyProofStructure(proof); if (!proofCheck.valid && proofCheck.error) { errors.push(proofCheck.error); } let signatureValid = false; let did; let kid; if (proofCheck.structure && proof?.meta) { did = proof.meta.did; kid = proof.meta.kid; // Get mock identity for verification const mockProvider = (0, mock_identity_provider_1.getMockIdentityProvider)(); const identity = mockProvider.getIdentity("agent1") || mockProvider.getIdentity("agent2") || mockProvider.getIdentity("verifier1"); if (identity && identity.did === did && identity.kid === kid) { // Verify signature using mock implementation const canonicalMeta = canonicalizeJSON(proof.meta); const signatureCheck = verifySignature(canonicalMeta, proof.jws, identity.publicKey); signatureValid = signatureCheck.valid; if (!signatureValid && signatureCheck.error) { errors.push(signatureCheck.error); } } else { errors.push(`No matching identity found for DID: ${did}, KeyID: ${kid}`); } } // Verify session const sessionCheck = verifySession(proof?.meta?.sessionId, proof?.meta?.ts); if (!sessionCheck.valid && sessionCheck.error) { errors.push(sessionCheck.error); } if (sessionCheck.expired) { warnings.push("Session is expired"); } // Verify hashes if request/response provided if (request && proof?.meta?.requestHash) { const expectedRequestHash = generateHash(canonicalizeJSON(request)); if (expectedRequestHash !== proof.meta.requestHash) { errors.push("Request hash mismatch"); } } if (response && proof?.meta?.responseHash) { const expectedResponseHash = generateHash(canonicalizeJSON(response)); if (expectedResponseHash !== proof.meta.responseHash) { errors.push("Response hash mismatch"); } } const result = { valid: errors.length === 0, did, kid, signature: { valid: signatureValid, algorithm: "EdDSA", error: signatureValid ? undefined : "Signature verification failed", }, proof: proofCheck, session: sessionCheck, errors, warnings, }; return test_1.LocalVerificationResultSchema.parse(result); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown verification error"; return test_1.LocalVerificationResultSchema.parse({ valid: false, signature: { valid: false, algorithm: "EdDSA", error: "Verification failed", }, proof: { valid: false, structure: false, timestamps: false, hashes: false, error: errorMessage, }, session: { valid: false, expired: false, error: "Session verification failed", }, errors: [errorMessage], warnings: [], }); } } /** * Verify DID document locally (mock implementation) */ async function verifyDIDDocumentLocally(did) { (0, test_environment_1.ensureTestMode)(); try { const mockProvider = (0, mock_identity_provider_1.getMockIdentityProvider)(); const identities = mockProvider.getAllIdentities(); // Find identity with matching DID const identity = Object.values(identities).find((id) => id.did === did); if (!identity) { return { valid: false, error: `No identity found for DID: ${did}`, }; } // Generate mock DID document const document = { "@context": ["https://www.w3.org/ns/did/v1"], id: did, verificationMethod: [ { id: `${did}#${identity.kid}`, type: "Ed25519VerificationKey2020", controller: did, publicKeyMultibase: `z${identity.publicKey}`, }, ], authentication: [`${did}#${identity.kid}`], assertionMethod: [`${did}#${identity.kid}`], }; return { valid: true, document, }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : "Unknown DID verification error", }; } } /** * Create a mock proof for testing */ function createMockProof(options) { (0, test_environment_1.ensureTestMode)(); const mockProvider = (0, mock_identity_provider_1.getMockIdentityProvider)(); const identity = mockProvider.getIdentity("agent1"); if (!identity) { throw new Error(`${test_1.TEST_ERROR_CODES.LOCAL_VERIFICATION_FAILED}: No test identity available`); } const now = Math.floor(Date.now() / 1000); const did = options.did || identity.did; const kid = options.kid || identity.kid; const sessionId = options.sessionId || "sess_test_mock"; const nonce = options.nonce || "mock_nonce"; const audience = options.audience || "test.example.com"; const requestHash = options.request ? generateHash(canonicalizeJSON(options.request)) : "sha256:mock_request_hash"; const responseHash = options.response ? generateHash(canonicalizeJSON(options.response)) : "sha256:mock_response_hash"; const meta = { did, kid: kid, ts: now, nonce, audience, sessionId, requestHash, responseHash, }; // Generate mock JWS signature const canonicalMeta = canonicalizeJSON(meta); const jws = (0, crypto_1.createHash)("sha256") .update(canonicalMeta + identity.publicKey) .digest("base64"); return { jws, meta, }; }