UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

244 lines (243 loc) 9.04 kB
"use strict"; /** * Proof Generation for XMCP-I Runtime * * Handles JCS canonicalization, SHA-256 digest generation, and Ed25519 JWS signing (compact format) * according to requirements 5.1, 5.2, 5.3, 5.6. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ProofGenerator = void 0; exports.createProofResponse = createProofResponse; exports.extractCanonicalData = extractCanonicalData; const crypto_1 = require("crypto"); const jose_1 = require("jose"); const json_canonicalize_1 = require("json-canonicalize"); const mcp_i_core_1 = require("@kya-os/mcp-i-core"); const node_providers_1 = require("../providers/node-providers"); /** * Proof generator class */ class ProofGenerator { identity; constructor(identity) { this.identity = identity; } /** * Generate proof for tool request/response * Requirements: 5.1, 5.2, 5.3, 5.6 */ async generateProof(request, response, session, options = {}) { // Generate canonical hashes const hashes = this.generateCanonicalHashes(request, response); // Create proof metadata const meta = { did: this.identity.did, kid: this.identity.kid, ts: Math.floor(Date.now() / 1000), nonce: session.nonce, audience: session.audience, sessionId: session.sessionId, requestHash: hashes.requestHash, responseHash: hashes.responseHash, ...options, // Include scopeId, delegationRef, and clientDid if provided }; // Generate JWS (compact format) const jws = await this.generateJWS(meta); return { jws, meta, }; } /** * Generate canonical hashes for request and response * Requirement: 5.1 */ generateCanonicalHashes(request, response) { // Canonicalize request (exclude transport metadata, include method and params) const canonicalRequest = { method: request.method, ...(request.params && { params: request.params }), }; // Canonicalize response (only the data part, exclude meta) const canonicalResponse = response.data; // Generate SHA-256 hashes with JCS canonicalization const requestHash = this.generateSHA256Hash(canonicalRequest); const responseHash = this.generateSHA256Hash(canonicalResponse); return { requestHash, responseHash, }; } /** * Generate SHA-256 hash with JCS canonicalization * Requirement: 5.2 */ generateSHA256Hash(data) { // JCS (JSON Canonicalization Scheme) canonicalization const canonicalJson = this.canonicalizeJSON(data); // Generate SHA-256 hash const hash = (0, crypto_1.createHash)("sha256") .update(canonicalJson, "utf8") .digest("hex"); return `sha256:${hash}`; } /** * JCS canonicalization implementation (RFC 8785) */ canonicalizeJSON(obj) { return (0, json_canonicalize_1.canonicalize)(obj); } /** * Generate Ed25519 JWS in compact format (header.payload.signature) * Requirement: 5.3 * * Uses standard JWT claims (aud, sub, iss) in addition to custom claims */ async generateJWS(meta) { try { // Import the private key const privateKeyPem = this.formatPrivateKeyAsPEM(this.identity.privateKey); const privateKey = await (0, jose_1.importPKCS8)(privateKeyPem, "EdDSA"); // Create JWT payload with standard claims + custom proof data // Standard JWT claims: aud (audience), sub (subject), iss (issuer) // Custom claims: requestHash, responseHash, nonce, sessionId, etc. const payload = { // Standard JWT claims (RFC 7519) aud: meta.audience, // Audience (who the token is for) sub: meta.did, // Subject (agent DID) iss: meta.did, // Issuer (agent DID - self-issued) // Custom MCP-I proof claims requestHash: meta.requestHash, responseHash: meta.responseHash, ts: meta.ts, nonce: meta.nonce, sessionId: meta.sessionId, // Optional claims ...(meta.scopeId && { scopeId: meta.scopeId }), ...(meta.delegationRef && { delegationRef: meta.delegationRef }), ...(meta.clientDid && { clientDid: meta.clientDid }), }; // Create and sign JWT (compact format: header.payload.signature) const jwt = await new jose_1.SignJWT(payload) .setProtectedHeader({ alg: "EdDSA", kid: this.identity.kid, }) .sign(privateKey); // Return full compact JWS (NOT detached) return jwt; } catch (error) { throw new Error(`Failed to generate JWS: ${error instanceof Error ? error.message : "Unknown error"}`); } } /** * Format base64 private key as PKCS#8 PEM for JOSE library */ formatPrivateKeyAsPEM(base64PrivateKey) { const keyData = Buffer.from(base64PrivateKey, "base64"); // Ed25519 PKCS#8 header and footer const header = "-----BEGIN PRIVATE KEY-----\n"; const footer = "\n-----END PRIVATE KEY-----"; // Wrap Ed25519 raw key in PKCS#8 structure (ASN.1 encoding) const pkcs8Header = Buffer.from([ 0x30, 0x2e, // SEQUENCE, length 46 0x02, 0x01, 0x00, // INTEGER version 0 0x30, 0x05, // SEQUENCE, length 5 0x06, 0x03, 0x2b, 0x65, 0x70, // OID for Ed25519 0x04, 0x22, // OCTET STRING, length 34 0x04, 0x20, // OCTET STRING, length 32 (the actual key) ]); const fullKey = Buffer.concat([pkcs8Header, keyData.subarray(0, 32)]); const base64Key = fullKey.toString("base64"); // Format as PEM with line breaks every 64 characters const formattedKey = base64Key.match(/.{1,64}/g)?.join("\n") || base64Key; return header + formattedKey + footer; } /** * Verify a proof (for testing/validation) */ async verifyProof(proof, request, response) { try { // Regenerate hashes const expectedHashes = this.generateCanonicalHashes(request, response); // Check if hashes match if (proof.meta.requestHash !== expectedHashes.requestHash || proof.meta.responseHash !== expectedHashes.responseHash) { return false; } // Verify JWS signature using CryptoService const publicKeyJwk = this.base64PublicKeyToJWK(this.identity.publicKey); const cryptoProvider = new node_providers_1.NodeCryptoProvider(); const cryptoService = new mcp_i_core_1.CryptoService(cryptoProvider); const isValid = await cryptoService.verifyJWS(proof.jws, publicKeyJwk, { expectedKid: this.identity.kid, alg: "EdDSA", }); return isValid; } catch (error) { console.error("[ProofGenerator] Proof verification error:", error); return false; } } /** * Convert base64 public key to Ed25519 JWK format */ base64PublicKeyToJWK(publicKeyBase64) { // Decode base64 to bytes const publicKeyBytes = Buffer.from(publicKeyBase64, "base64"); // Verify key length (Ed25519 public keys are 32 bytes) if (publicKeyBytes.length !== 32) { throw new Error(`Invalid Ed25519 public key length: ${publicKeyBytes.length}`); } // Convert to base64url encoding const base64url = Buffer.from(publicKeyBytes) .toString("base64") .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=/g, ""); return { kty: "OKP", crv: "Ed25519", x: base64url, kid: this.identity.kid, }; } } exports.ProofGenerator = ProofGenerator; /** * Utility functions */ /** * Create a tool response with proof */ async function createProofResponse(request, data, identity, session, options = {}) { const response = { data }; const proofGenerator = new ProofGenerator(identity); const proof = await proofGenerator.generateProof(request, response, session, options); response.meta = { proof }; return response; } /** * Extract canonical data for hashing (utility for testing) */ function extractCanonicalData(request, response) { return { request: { method: request.method, ...(request.params && { params: request.params }), }, response: response.data, }; }