(function (window) { "use strict"; const keyRequests = new Map(); const encoder = new TextEncoder(); function toBase64Url(bytes) { let binary = ""; const view = new Uint8Array(bytes); for (let offset = 0; offset < view.length; offset += 0x8000) { binary += String.fromCharCode(...view.subarray(offset, offset + 0x8000)); } return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } function fromBase64Url(text) { const normalized = text.replace(/-/g, "+").replace(/_/g, "/"); const binary = atob(normalized + "=".repeat((4 - normalized.length % 4) % 4)); return Uint8Array.from(binary, (character) => character.charCodeAt(0)); } function fetchKey(keyUrl) { if (!keyUrl) { return Promise.reject(new Error("A payload encryption key URL is required.")); } if (!keyRequests.has(keyUrl)) { const request = fetch(keyUrl, { credentials: "same-origin", headers: { Accept: "application/json" } }).then(async (response) => { const body = await response.json(); if (!response.ok) throw body; if (!body.keyId || !body.publicKey) { throw new Error("The payload encryption key response is invalid."); } return body; }).catch((error) => { keyRequests.delete(keyUrl); throw error; }); keyRequests.set(keyUrl, request); } return keyRequests.get(keyUrl); } async function encrypt(payload, options) { if (!window.crypto?.subtle) { throw new Error("Secure payload encryption is not supported by this browser."); } const keyInfo = await fetchKey(options?.keyUrl); const publicKey = await window.crypto.subtle.importKey( "spki", fromBase64Url(keyInfo.publicKey), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"] ); const aesKey = await window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, ["encrypt"] ); const initializationVector = window.crypto.getRandomValues(new Uint8Array(12)); const requestId = window.crypto.randomUUID(); const timestamp = new Date().toISOString(); const additionalData = encoder.encode(`${keyInfo.keyId}|${requestId}|${timestamp}`); const encryptedPayload = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: initializationVector, additionalData }, aesKey, encoder.encode(JSON.stringify(payload)) ); const rawKey = await window.crypto.subtle.exportKey("raw", aesKey); const encryptedKey = await window.crypto.subtle.encrypt( { name: "RSA-OAEP" }, publicKey, rawKey ); return Object.freeze({ keyId: keyInfo.keyId, encryptedKey: toBase64Url(encryptedKey), initializationVector: toBase64Url(initializationVector), encryptedPayload: toBase64Url(encryptedPayload), requestId, timestamp }); } function clearKeyCache(keyUrl) { if (keyUrl) keyRequests.delete(keyUrl); else keyRequests.clear(); } window.CygnusPayloadCrypto = Object.freeze({ encrypt, clearKeyCache }); }(window));