"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.aes_256_gcm_siv = exports.deriveKeys = void 0; const utils_js_1 = require("../utils.js"); const _polyval_js_1 = require("../_polyval.js"); const utils_js_2 = require("./utils.js"); /** * AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance. * RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452 */ // AES stuff (same as ff1) const BLOCK_LEN = 16; const IV = new Uint8Array(BLOCK_LEN); async function encryptBlock(msg, key) { if (key.length !== 16 && key.length !== 32) throw new Error('Invalid key length'); const mode = { name: `AES-CBC`, length: key.length * 8 }; const cr = (0, utils_js_2.getWebcryptoSubtle)(); const wKey = await cr.importKey('raw', key, mode, true, ['encrypt']); const cipher = await cr.encrypt({ name: `aes-cbc`, iv: IV, counter: IV, length: 64 }, wKey, msg); return new Uint8Array(cipher).subarray(0, 16); } // Kinda constant-time equality function equalBytes(a, b) { // Should not happen if (a.length !== b.length) throw new Error('equalBytes: Different size of Uint8Arrays'); let flag = true; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) flag && (flag = false); return flag; } // Wrap position so it will be in padded to blockSize const wrapPos = (pos, blockSize) => Math.ceil(pos / blockSize) * blockSize; const limit = (name, min, max) => (value) => { if (!Number.isSafeInteger(value) || min > value || value > max) throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`); }; // From RFC 8452: Section 6 const AAD_LIMIT = limit('AAD', 0, 2 ** 36); const PLAIN_LIMIT = limit('Plaintext', 0, 2 ** 36); const NONCE_LIMIT = limit('Nonce', 12, 12); const CIPHER_LIMIT = limit('Ciphertext', 16, 2 ** 36 + 16); // nodejs api doesn't support 32bit counters, browser does async function ctr(key, tag, input) { // The initial counter block is the tag with the most significant bit of the last byte set to one. let block = tag.slice(); block[15] |= 0x80; let view = (0, utils_js_1.createView)(block); let output = new Uint8Array(input.length); for (let pos = 0; pos < input.length;) { const encryptedBlock = await encryptBlock(block, key); view.setUint32(0, view.getUint32(0, true) + 1, true); const take = Math.min(input.length, encryptedBlock.length); for (let j = 0; j < take; j++, pos++) output[pos] = encryptedBlock[j] ^ input[pos]; } return new Uint8Array(output); } async function deriveKeys(key, nonce) { NONCE_LIMIT(nonce.length); const len = key.length; if (len !== 16 && len !== 32) throw new Error(`key length must be 16 or 32 bytes, got: ${len} bytes`); const encKey = new Uint8Array(len); const authKey = new Uint8Array(16); let counter = 0; const deriveBlock = new Uint8Array(nonce.length + 4); deriveBlock.set(nonce, 4); const view = (0, utils_js_1.createView)(deriveBlock); for (const derivedKey of [authKey, encKey]) { for (let i = 0; i < derivedKey.length; i += 8) { view.setUint32(0, counter++, true); const block = await encryptBlock(deriveBlock, key); derivedKey.set(block.subarray(0, 8), i); } } return { authKey, encKey }; } exports.deriveKeys = deriveKeys; async function aes_256_gcm_siv(key, nonce, AAD) { const { encKey, authKey } = await deriveKeys(key, nonce); const computeTag = async (data, AAD) => { const dataPos = wrapPos(AAD.length, 16); const lenPos = wrapPos(dataPos + data.length, 16); const block = new Uint8Array(lenPos + 16); const view = (0, utils_js_1.createView)(block); block.set(AAD); block.set(data, dataPos); (0, utils_js_1.setBigUint64)(view, lenPos, BigInt(AAD.length * 8), true); (0, utils_js_1.setBigUint64)(view, lenPos + 8, BigInt(data.length * 8), true); // Compute the expected tag by XORing S_s and the nonce, clearing the // most significant bit of the last byte and encrypting with the // message-encryption key. const tag = (0, _polyval_js_1.polyval)(authKey, block); for (let i = 0; i < 12; i++) tag[i] ^= nonce[i]; // Clear the highest bit tag[15] &= 0x7f; return await encryptBlock(tag, encKey); }; return { // computeTag, encrypt: async (plaintext) => { AAD_LIMIT(AAD.length); PLAIN_LIMIT(plaintext.length); const tag = await computeTag(plaintext, AAD); const out = new Uint8Array(plaintext.length + 16); out.set(tag, plaintext.length); out.set(await ctr(encKey, tag, plaintext)); return out; }, decrypt: async (ciphertext) => { CIPHER_LIMIT(ciphertext.length); AAD_LIMIT(AAD.length); const tag = ciphertext.subarray(-16); const plaintext = await ctr(encKey, tag, ciphertext.subarray(0, -16)); const expectedTag = await computeTag(plaintext, AAD); if (!equalBytes(tag, expectedTag)) throw new Error('invalid poly1305 tag'); return plaintext; }, }; } exports.aes_256_gcm_siv = aes_256_gcm_siv; //# sourceMappingURL=siv.js.map