← back to AbramsOS

tests/crypto.test.js

66 lines

// AES-256-GCM crypto helper tests.
// We need a 64-hex-char ENCRYPTION_KEY in env; load it from .env if present,
// otherwise inject a deterministic test key.

const test = require('node:test');
const assert = require('node:assert');

require('dotenv').config();
if (!process.env.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY.length < 64) {
  process.env.ENCRYPTION_KEY = 'a'.repeat(64);
}

const { encrypt, decrypt } = require('../lib/crypto');

test('round-trip plaintext returns same value', () => {
  const plain = 'gmail-refresh-token-1//04abc-supersecret';
  const { ciphertext, iv, tag } = encrypt(plain);
  const decoded = decrypt(ciphertext, iv, tag);
  assert.strictEqual(decoded, plain);
});

test('IV differs between encryptions of the same plaintext', () => {
  const plain = 'same-plaintext';
  const a = encrypt(plain);
  const b = encrypt(plain);
  assert.notStrictEqual(a.iv.toString('hex'), b.iv.toString('hex'), 'IVs should be random');
  // Both decrypt back to the same plaintext
  assert.strictEqual(decrypt(a.ciphertext, a.iv, a.tag), plain);
  assert.strictEqual(decrypt(b.ciphertext, b.iv, b.tag), plain);
});

test('tampering with ciphertext throws', () => {
  const { ciphertext, iv, tag } = encrypt('hello');
  const tampered = Buffer.from(ciphertext);
  tampered[0] ^= 0xff;
  assert.throws(() => decrypt(tampered, iv, tag), /Unsupported state|unable to authenticate/i);
});

test('tampering with auth tag throws', () => {
  const { ciphertext, iv, tag } = encrypt('hello');
  const tamperedTag = Buffer.from(tag);
  tamperedTag[0] ^= 0xff;
  assert.throws(() => decrypt(ciphertext, iv, tamperedTag), /Unsupported state|unable to authenticate/i);
});

test('handles empty string', () => {
  const { ciphertext, iv, tag } = encrypt('');
  assert.strictEqual(decrypt(ciphertext, iv, tag), '');
});

test('handles unicode', () => {
  const plain = 'héllo · 世界 · 🌍';
  const { ciphertext, iv, tag } = encrypt(plain);
  assert.strictEqual(decrypt(ciphertext, iv, tag), plain);
});

test('encrypt rejects when ENCRYPTION_KEY is missing or too short', () => {
  const orig = process.env.ENCRYPTION_KEY;
  process.env.ENCRYPTION_KEY = '';
  // Re-require to pick up env change — but our crypto reads env at call time, so just call:
  assert.throws(() => encrypt('x'), /ENCRYPTION_KEY/);
  process.env.ENCRYPTION_KEY = '0123456789abcdef'; // 16 chars (too short)
  assert.throws(() => encrypt('x'), /ENCRYPTION_KEY/);
  process.env.ENCRYPTION_KEY = orig;
});