Menguji enkripsi ujung-ke-ujung (End-to-End Encryption / E2EE) pada protokol terdistribusi seperti ActivityPub menghadirkan tantangan unik di pipeline Continuous Integration (CI). Arsitektur federasi mengandalkan pengiriman pesan asinkron, latensi antar-instance yang tidak menentu, serta state kriptografi yang harus sinkron tanpa server pusat. Menjalankan verifikasi end-to-end encryption protokol terdistribusi di CI membutuhkan pendekatan deterministik agar pengujian berjalan cepat dan bebas dari kegagalan acak (flaky tests).

Tantangan E2EE pada Federasi ActivityPub

Pada ActivityPub standar, server mengantarkan objek melalui HTTP POST ke endpoint inbox milik instance penerima. Saat lapisan E2EE ditambahkan (misalnya menggunakan adaptasi Double Ratchet atau enkripsi berbasis envelope WebPush/PGP), payload dienkripsi oleh klien pengirim dan server hanya bertindak sebagai relai buta (blind relay).

Hambatan utama pengujian skenario ini di CI meliputi:

  • Non-deterministik key generation: Pembangkitan pasangan kunci berbasis CSPRNG dinamis menyulitkan assertion langsung terhadap ciphertext atau reproducible state.
  • Latensi federasi asinkron: Pengiriman kunci publik via discovery actor (WebFinger/Actor JSON-LD) sering memicu race condition dalam eksekusi test runner paralel.
  • Urutan pengiriman tidak terjamin: Protokol transport terdistribusi dapat mengirimkan pesan keluar dari urutan asli (out-of-order delivery), yang berpotensi merusak state ratchet penerima jika tidak ditangani dengan benar.

Strategi Pengujian Deterministik di Pipeline CI

1. Key Exchange Deterministik Melalui Fixed Entropy

Jangan gunakan mocking pada fungsi kriptografi inti. Mocking menyamarkan kesalahan implementasi padding, serialisasi ASN.1/JWK, atau enkapsulasi nonce. Sebaliknya, gunakan fungsi pembangkit kunci nyata namun dengan seed entropi statis (RFC 8032 / HKDF dengan fixed salt).

2. Menghilangkan Flaky Tests pada Async Key Delivery

Hindari penggunaan sleep() untuk menunggu pertukaran kunci antar-node. Implementasikan in-memory transport mock atau custom loopback HTTP handler yang menggunakan polling berbasis predikat eksplisit (wait-until) dengan timeout deterministik. Pola ini memutus ketergantungan pada network jitter sistem operasi di lingkungan CI.

3. Uji Regresi Backward Compatibility

Ketika skema metadata enkripsi diperbarui (misalnya perubahan format envelop JSON-LD atau migrasi cipher suite), CI harus memverifikasi bahwa parser versi terbaru tetap mampu mendekripsi ciphertext yang dihasilkan oleh artefak versi sebelumnya. Simpan serangkaian vektor uji pesan (ciphertext, ephemeral public key, dan expected plaintext) statis di repositori sebagai baseline regresi.

Implementasi: Assertion Dekripsi Out-of-Order Tanpa Eksternal Framework

Contoh berikut mendemonstrasikan pengujian mandiri menggunakan runtime Node.js native (modul crypto) tanpa framework testing berat. Kode ini menguji kemampuan penerima menangani pesan terenkripsi yang tiba tidak berurutan dengan menyinkronkan counter pesan secara benar.

const crypto = require('crypto');
const assert = require('assert');

// Simulasi Envelope E2EE Sederhana
class E2EESession {
  constructor(sharedSecret) {
    this.sharedSecret = sharedSecret;
    this.inboundCounters = new Set();
  }

  encrypt(plaintext, sequence) {
    const iv = crypto.randomBytes(12);
    // Turunkan sub-key per-pesan menggunakan urutan sebagai info KDF
    const messageKey = crypto.hkdfSync('sha256', this.sharedSecret, '', Buffer.from(sequence.toString()), 32);
    const cipher = crypto.createCipheriv('aes-256-gcm', messageKey, iv);
    
    let encrypted = cipher.update(plaintext, 'utf8');
    encrypted = Buffer.concat([encrypted, cipher.final()]);
    const tag = cipher.getAuthTag();

    return {
      sequence,
      iv: iv.toString('hex'),
      tag: tag.toString('hex'),
      ciphertext: encrypted.toString('hex')
    };
  }

  decrypt(payload) {
    const { sequence, iv, tag, ciphertext } = payload;
    
    // Cegah replay attack
    if (this.inboundCounters.has(sequence)) {
      throw new Error(`Replay detected for message sequence: ${sequence}`);
    }

    const messageKey = crypto.hkdfSync('sha256', this.sharedSecret, '', Buffer.from(sequence.toString()), 32);
    const decipher = crypto.createDecipheriv('aes-256-gcm', messageKey, Buffer.from(iv, 'hex'));
    decipher.setAuthTag(Buffer.from(tag, 'hex'));

    let decrypted = decipher.update(Buffer.from(ciphertext, 'hex'), null, 'utf8');
    decrypted += decipher.final('utf8');

    // Catat pesan yang berhasil diproses meski tiba acak
    this.inboundCounters.add(sequence);
    return decrypted;
  }
}

// Runnable Test Suite di CI
function testOutOfOrderDecryption() {
  const deterministicSecret = Buffer.alloc(32, 0x42); // Deterministic seed
  const sender = new E2EESession(deterministicSecret);
  const receiver = new E2EESession(deterministicSecret);

  // Buat pesan urut
  const msg1 = sender.encrypt('Halo Node Federasi!', 1);
  const msg2 = sender.encrypt('Pesan kedua mendahului.', 2);
  const msg3 = sender.encrypt('Pesan ketiga terakhir.', 3);

  // Simulasi pengiriman out-of-order: 2 -> 1 -> 3
  const receivedPayloads = [msg2, msg1, msg3];

  const results = {};
  for (const payload of receivedPayloads) {
    results[payload.sequence] = receiver.decrypt(payload);
  }

  // Verifikasi isi plaintext sesuai sequence
  assert.strictEqual(results[1], 'Halo Node Federasi!');
  assert.strictEqual(results[2], 'Pesan kedua mendahului.');
  assert.strictEqual(results[3], 'Pesan ketiga terakhir.');

  // Verifikasi proteksi replay
  assert.throws(
    () => receiver.decrypt(msg2),
    /Replay detected/,
    'Kegagalan: Payload duplikat harus ditolak oleh receiver'
  );

  console.log('PASS: Verifikasi Out-of-Order Delivery & Replay Protection berhasil.');
}

testOutOfOrderDecryption();

Catatan Pipeline: Masukkan file pengujian mandiri tersebut langsung ke dalam step execution CI (misal: node test/e2ee-order.js) sebelum integrasi end-to-end yang lebih lambat dijalankan. Ini memberikan fast-fail feedback jika terjadi regresi logika kriptografi.

Checklist Integrasi pada CI Runner

  • Isolasi Port dan Instance: Gunakan Unix Domain Socket atau port acak terikat localhost jika menjalankan multiple mock federated nodes dalam satu runner.
  • Eksekusi Vektor Uji Kriptografi Statis: Jalankan verifikasi decrypt terhadap fixture file JSON yang di-commit ke repositori untuk memastikan backwards-compatibility parser.
  • Deterministic Pseudo-randomness: Konfigurasikan library pengujian untuk mencatat seed RNG saat terjadi failure, sehingga bug konkurensi atau key-ordering dapat direproduksi secara lokal.