UNPKG

cross-crypto-ts

Version:

Cifrado híbrido AES-GCM + RSA-OAEP con interoperabilidad entre TypeScript y Python, con diseño compatible para Rust.

132 lines (131 loc) 5.04 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.canonicalJson = canonicalJson; exports.canonicalJsonBytes = canonicalJsonBytes; exports.fingerprintBytes = fingerprintBytes; exports.fingerprintPublicKey = fingerprintPublicKey; exports.generateEd25519Keys = generateEd25519Keys; exports.signPayload = signPayload; exports.verifyPayload = verifyPayload; // src/sign.ts const crypto_1 = __importDefault(require("crypto")); function pemToArrayBuffer(pem) { const base64 = pem .replace(/-----BEGIN [^-]+-----/g, "") .replace(/-----END [^-]+-----/g, "") .replace(/\s+/g, ""); const buffer = Buffer.from(base64, "base64"); return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); } function bytesToBase64(bytes) { return Buffer.from(bytes).toString("base64"); } function base64ToArrayBuffer(value) { const buffer = Buffer.from(value, "base64"); return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); } function uint8ArrayToArrayBuffer(bytes) { return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); } function assertJsonSafe(value) { if (typeof value === "number" && !Number.isFinite(value)) { throw new Error("JSON canónico no permite NaN ni Infinity."); } if (Array.isArray(value)) { value.forEach(assertJsonSafe); return; } if (value && typeof value === "object") { for (const item of Object.values(value)) { assertJsonSafe(item); } } } function sortDeep(value) { if (Array.isArray(value)) { return value.map(sortDeep); } if (value && typeof value === "object") { const input = value; const output = {}; for (const key of Object.keys(input).sort()) { output[key] = sortDeep(input[key]); } return output; } return value; } function canonicalJson(payload) { assertJsonSafe(payload); return JSON.stringify(sortDeep(payload)); } function canonicalJsonBytes(payload) { return new TextEncoder().encode(canonicalJson(payload)); } function fingerprintBytes(data) { const buffer = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data)); return crypto_1.default.createHash("sha256").update(buffer).digest("hex"); } function fingerprintPublicKey(publicKeyPem) { return fingerprintBytes(pemToArrayBuffer(publicKeyPem)); } function generateEd25519Keys() { const { publicKey, privateKey } = crypto_1.default.generateKeyPairSync("ed25519"); return { publicKey: publicKey.export({ type: "spki", format: "pem", }), privateKey: privateKey.export({ type: "pkcs8", format: "pem", }), }; } async function signPayload(payload, privateKeyPem, options = {}) { const data = canonicalJsonBytes(payload); const privateKey = await crypto_1.default.webcrypto.subtle.importKey("pkcs8", pemToArrayBuffer(privateKeyPem), { name: "Ed25519" }, false, ["sign"]); const signature = await crypto_1.default.webcrypto.subtle.sign({ name: "Ed25519" }, privateKey, uint8ArrayToArrayBuffer(data)); return { alg: "Ed25519", keyId: options.keyId ?? "v1", signedAt: Math.floor(options.signedAt ?? Date.now() / 1000), payloadHash: fingerprintBytes(data), signature: bytesToBase64(signature), }; } async function verifyPayload(payload, signaturePayload, publicKeyPem, options = {}) { try { if (signaturePayload.alg !== "Ed25519") return false; const required = ["alg", "keyId", "signedAt", "payloadHash", "signature"]; for (const key of required) { if (!(key in signaturePayload)) return false; } const signedAt = Number(signaturePayload.signedAt); if (!Number.isFinite(signedAt)) return false; if (options.maxAgeSeconds !== undefined) { const now = Math.floor(options.now ?? Date.now() / 1000); if (signedAt > now + 30) return false; if (now - signedAt > options.maxAgeSeconds) return false; } const data = canonicalJsonBytes(payload); const expectedHash = fingerprintBytes(data); if (signaturePayload.payloadHash !== expectedHash) return false; const publicKey = await crypto_1.default.webcrypto.subtle.importKey("spki", pemToArrayBuffer(publicKeyPem), { name: "Ed25519" }, false, ["verify"]); return await crypto_1.default.webcrypto.subtle.verify({ name: "Ed25519" }, publicKey, base64ToArrayBuffer(String(signaturePayload.signature)), uint8ArrayToArrayBuffer(data)); } catch { return false; } }