import { generateKeyPairSync, createHash, sign, verify } from 'node:crypto';
// Tipos para configuración de claves
interface CryptoConfig {
privateKey: string;
publicKey: string;
serverId: string;
}
// Tipo para respuesta firmada del MCP
export interface SignedMCPResponse {
data: any; // Datos originales de la herramienta
hash: string; // SHA-256 hash de los datos JSON
signature: string; // Firma digital en base64
timestamp: string; // ISO 8601 timestamp
publicKey: string; // Clave pública para verificación
serverId: string; // Identificador único del servidor
version: string; // Versión del formato de firma
}
// Tipo para resultado de verificación
export interface VerificationResult {
isValid: boolean;
error?: string;
details: {
hashMatch: boolean;
signatureValid: boolean;
timestamp: string;
serverId: string;
};
}
/**
* Obtiene la configuración de claves según el entorno
*/
function getCryptoConfig(env?: any): CryptoConfig {
// Producción: usar secrets de Cloudflare Workers
if (env?.MCP_PRIVATE_KEY) {
return {
privateKey: env.MCP_PRIVATE_KEY.replace(/\\n/g, '\n'),
publicKey: env.MCP_PUBLIC_KEY.replace(/\\n/g, '\n'),
serverId: env.MCP_SERVER_ID || 'cv-mcp-server'
};
}
// Local: usar variables de entorno del proceso
if (process.env.MCP_PRIVATE_KEY) {
return {
privateKey: process.env.MCP_PRIVATE_KEY.replace(/\\n/g, '\n'),
publicKey: process.env.MCP_PUBLIC_KEY!.replace(/\\n/g, '\n'),
serverId: process.env.MCP_SERVER_ID || 'cv-mcp-server-local'
};
}
// Fallback: generar claves temporales para desarrollo rápido
console.warn('⚠️ Generando claves temporales. Ejecuta "npm run setup-keys" para desarrollo persistente.');
return generateTemporaryKeys();
}
/**
* Genera claves temporales para desarrollo sin configuración
*/
function generateTemporaryKeys(): CryptoConfig {
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
return {
privateKey,
publicKey,
serverId: 'cv-mcp-server-temp'
};
}
/**
* Crea hash SHA-256 de datos JSON
*/
function createDataHash(data: any): string {
const jsonString = JSON.stringify(data, null, 0); // Sin espacios para hash consistente
return createHash('sha256').update(jsonString).digest('hex');
}
/**
* Firma un hash con la clave privada
*/
function signHash(hash: string, privateKey: string): string {
const signature = sign('sha256', Buffer.from(hash, 'hex'), {
key: privateKey,
format: 'pem'
});
return signature.toString('base64');
}
/**
* Verifica una firma con la clave pública
*/
function verifySignature(hash: string, signature: string, publicKey: string): boolean {
try {
return verify(
'sha256',
Buffer.from(hash, 'hex'),
{
key: publicKey,
format: 'pem'
},
Buffer.from(signature, 'base64')
);
} catch (error) {
console.error('Error verificando firma:', error);
return false;
}
}
/**
* Clase principal para operaciones criptográficas
*/
export class MCPCrypto {
private config: CryptoConfig;
constructor(env?: any) {
this.config = getCryptoConfig(env);
}
/**
* Firma una respuesta MCP con todos los metadatos necesarios
*/
signResponse(data: any): SignedMCPResponse {
// Crear hash de los datos
const hash = createDataHash(data);
// Firmar el hash
const signature = signHash(hash, this.config.privateKey);
// Crear respuesta firmada completa
const signedResponse: SignedMCPResponse = {
data,
hash,
signature,
timestamp: new Date().toISOString(),
publicKey: this.config.publicKey,
serverId: this.config.serverId,
version: '1.0'
};
return signedResponse;
}
/**
* Verifica la integridad y autenticidad de una respuesta firmada
*/
verifySignedResponse(signedResponse: SignedMCPResponse): VerificationResult {
try {
// Verificar que tenga todos los campos necesarios
if (!signedResponse.data || !signedResponse.hash || !signedResponse.signature) {
return {
isValid: false,
error: 'Respuesta firmada incompleta',
details: {
hashMatch: false,
signatureValid: false,
timestamp: signedResponse.timestamp || 'missing',
serverId: signedResponse.serverId || 'missing'
}
};
}
// Verificar hash de los datos
const computedHash = createDataHash(signedResponse.data);
const hashMatch = computedHash === signedResponse.hash;
// Verificar firma digital
const signatureValid = verifySignature(
signedResponse.hash,
signedResponse.signature,
signedResponse.publicKey
);
const isValid = hashMatch && signatureValid;
return {
isValid,
error: isValid ? undefined : 'Hash o firma inválidos',
details: {
hashMatch,
signatureValid,
timestamp: signedResponse.timestamp,
serverId: signedResponse.serverId
}
};
} catch (error) {
return {
isValid: false,
error: `Error durante verificación: ${error}`,
details: {
hashMatch: false,
signatureValid: false,
timestamp: signedResponse.timestamp || 'unknown',
serverId: signedResponse.serverId || 'unknown'
}
};
}
}
/**
* Obtiene la clave pública para compartir con clientes
*/
getPublicKey(): string {
return this.config.publicKey;
}
/**
* Obtiene el ID del servidor
*/
getServerId(): string {
return this.config.serverId;
}
}
/**
* Helper para crear respuesta MCP firmada de forma simple
*/
export function createSignedMCPResponse(data: any, env?: any): SignedMCPResponse {
const crypto = new MCPCrypto(env);
return crypto.signResponse(data);
}