// Interop-Test v3: lädt EXAKT den Core aus der ausgelieferten index.html und // prüft Byte-Kompatibilität mit dem Python-CLI (richtungsgetrennte Schlüssel, // SPS3 mit Poly1305-Einmal-MAC). Orchestriert von _interop_run.sh. import { readFileSync, writeFileSync } from 'node:fs'; const U = (p) => new Uint8Array(readFileSync(new URL(p, import.meta.url))); const html = readFileSync(new URL('./index.html', import.meta.url), 'utf8'); const core = html.match(/==CORE START==.*?\*\/([\s\S]*?)\/\*\s*==CORE END==/)[1]; new Function(core)(); const SP = globalThis.SP; let fails = 0; const ok = (c, m) => { console.log((c ? ' ✓ ' : ' ✗ ') + m); if (!c) fails++; }; const hex = (s) => new Uint8Array(s.match(/../g).map((h) => parseInt(h, 16))); // 0) Poly1305 gegen den RFC-8439-Testvektor (§2.5.2) — verankert beide Impls. { console.log('0) Poly1305 RFC 8439'); const key = hex('85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b'); const msg = new TextEncoder().encode('Cryptographic Forum Research Group'); const tag = SP.poly1305(msg, key); ok([...tag].map((b) => b.toString(16).padStart(2, '0')).join('') === 'a8061dc1305136c6c22b8baf0c0127a9', 'Tag matcht RFC-Vektor'); } // A: CLI (send.key, SPS3) -> Web-Core (recv.key): extrahieren, MAC prüfen, entschlüsseln { const rgb = U('./_a_rgb.bin'); const recv = SP.parseKey(U('../t2/rel_partner_seite__recv.key')); const info = SP.extract(rgb); console.log('A) CLI → Web'); ok(info.version === SP.STEGO_VERSION, 'Container ist SPS3'); ok(SP.sameStream(info.streamId, recv.streamId), 'stream_id passt zum recv.key'); const macKey = recv.material.slice(info.offset + info.ciphertext.length, info.offset + info.ciphertext.length + SP.MAC_KEY_LEN); const expected = SP.poly1305(SP.concat(info.macInputHeader, info.ciphertext), macKey); ok(SP.ctEqual(expected, info.tag), 'MAC verifiziert (CLI-Tag == Web-Tag)'); const plain = SP.otpXor(info.ciphertext, recv.material, info.offset); const text = new TextDecoder().decode(plain); ok(text === 'MsgA vom CLI 🕵️', 'Klartext: ' + JSON.stringify(text)); // Manipulation: ein Ciphertext-Bit kippen -> MAC muss anschlagen. const bad = info.ciphertext.slice(); bad[0] ^= 1; const badTag = SP.poly1305(SP.concat(info.macInputHeader, bad), macKey); ok(!SP.ctEqual(badTag, info.tag), 'gekipptes Bit wird vom MAC erkannt'); } // B: Web-Core (send.key, SPS3) -> CLI (recv.key) { const rgb = U('./_b_rgb.bin'); const send = SP.parseKey(U('../t2/rel_partner_seite__send.key')); const msg = 'MsgB aus dem Browser-Core 🌐'; console.log('B) Web → CLI'); ok(send.role === SP.ROLE_SEND, 'partner send.key hat Rolle SEND'); const plain = new TextEncoder().encode(msg); const ct = SP.otpXor(plain, send.material, send.sendOffset); const macKey = send.material.slice(send.sendOffset + plain.length, send.sendOffset + plain.length + SP.MAC_KEY_LEN); const payload = SP.buildStego(send.streamId, send.sendOffset, ct, macKey); SP.embed(rgb, payload); writeFileSync(new URL('./_b_out_rgb.bin', import.meta.url), rgb); ok(true, `eingebettet (${plain.length} B ab Offset ${send.sendOffset}) → _b_out_rgb.bin`); } process.exit(fails ? 1 : 0);