Skip to content

Reference source · 0.1.0-dev.11

opaque.ts

src/opaque.ts · Download original file

import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
import { b64, unb64, jsonBytes, parseObject, requireThat, ProtocolError } from './core.ts';

// Host-internal AEAD for opaque cursors and encrypted recovery receipts, not a wire crypto suite.
export function protect(body: Record<string, any>, key: Buffer, context: Buffer): string {
  requireThat(key.length === 32);
  const nonce = randomBytes(12), clear = jsonBytes(body);
  try {
    const cipher = createCipheriv('aes-256-gcm', key, nonce); cipher.setAAD(context);
    const ciphertext = Buffer.concat([cipher.update(clear), cipher.final()]);
    return b64(Buffer.concat([nonce, cipher.getAuthTag(), ciphertext]));
  } finally { clear.fill(0); }
}
export function reveal(value: string, key: Buffer, context: Buffer): Record<string, any> {
  requireThat(typeof value === 'string' && value.length <= 12000 && key.length === 32, 'invalid_opaque_value');
  const bytes = unb64(value); requireThat(bytes.length >= 30, 'invalid_opaque_value');
  let clear: Buffer | undefined;
  try {
    const decipher = createDecipheriv('aes-256-gcm', key, bytes.subarray(0, 12));
    decipher.setAAD(context); decipher.setAuthTag(bytes.subarray(12, 28));
    clear = Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]);
    return parseObject(clear);
  } catch { throw new ProtocolError('invalid_opaque_value'); }
  finally { clear?.fill(0); }
}