import crypto from "crypto"; /** * Verify POPO webhook signature. * Signature = SHA256(token + nonce + timestamp) */ export function verifySignature(params: { token: string; nonce: string; timestamp: string; signature: string; }): boolean { const { token, nonce, timestamp, signature } = params; const data = token + nonce + timestamp; const computed = crypto.createHash("sha256").update(data).digest("hex"); return computed.toLowerCase() === signature.toLowerCase(); } /** * Decrypt POPO encrypted message using AES-CBC. * Key derivation: Base64 decode(aesKey + "=") -> 32 bytes, first 16 bytes = IV */ export function decryptMessage(encrypt: string, aesKey: string): string { // POPO uses Base64(aesKey + "=") as the key source const keyBuffer = Buffer.from(aesKey + "=", "base64"); if (keyBuffer.length < 32) { throw new Error("Invalid AES key length"); } // First 16 bytes as IV, full 32 bytes as key const iv = keyBuffer.subarray(0, 16); const key = keyBuffer.subarray(0, 32); // Decrypt the base64-encoded ciphertext const ciphertext = Buffer.from(encrypt, "base64"); const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv); let decrypted = decipher.update(ciphertext); decrypted = Buffer.concat([decrypted, decipher.final()]); // Remove PKCS7 padding and parse as UTF-8 return decrypted.toString("utf8"); } /** * Encrypt POPO response message using AES-CBC. * Used to encrypt the "success" response. */ export function encryptMessage(plaintext: string, aesKey: string): string { const keyBuffer = Buffer.from(aesKey + "=", "base64"); if (keyBuffer.length < 32) { throw new Error("Invalid AES key length"); } const iv = keyBuffer.subarray(0, 16); const key = keyBuffer.subarray(0, 32); const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); let encrypted = cipher.update(plaintext, "utf8"); encrypted = Buffer.concat([encrypted, cipher.final()]); return encrypted.toString("base64"); } /** * Generate a random nonce for webhook responses. */ export function generateNonce(): string { return crypto.randomBytes(16).toString("hex"); }