← terug naar Betaal-QR

Zo controleer je dit zelf

Betaal-QR belooft dat je betaalgegevens dit apparaat niet verlaten, en dat de site gebouwd is uit de openbare broncode. Je hoeft dat niet op mijn woord te geloven. Hieronder staat hoe je het zelf nagaat, van eenvoudig (een paar klikken) tot grondig (zelf bouwen en vergelijken).

1Wat wordt beloofd

2Kijk zelf naar het netwerkverkeer

Dit kost een paar minuten en vraagt geen technische kennis.

  1. Open Betaal-QR en druk op F12 (Mac: Cmd+Option+I). Kies het tabblad Netwerk (Network) en laad de pagina opnieuw.
  2. Je ziet alleen bestanden van betaal.terhofte.net langskomen.
  3. Wis de lijst en vul een betaling in. Er komen geen nieuwe verzoeken bij, ook niet als de QR-code verschijnt of als je een ontvanger bewaart.
  4. Open een factuur (PDF). De eerste keer zie je de PDF-lezer (pdflezer.mjs en de bestanden van pdf.js en jsQR) van betaal.terhofte.net komen. Je factuur zelf zie je niet langskomen: die wordt niet verstuurd.
  5. Nog eenvoudiger: zet de wifi uit (of vliegtuigmodus aan) en maak een code. Na het eerste bezoek werkt alles gewoon.

3De browser dwingt het af

De pagina geeft je browser een Content-Security-Policy mee: een regel die bepaalt wat de pagina mag. In de app staat hij onder Wat deze pagina mag. Zo luidt hij:

default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' blob: data:; manifest-src 'self'; worker-src 'self'; connect-src 'none'; base-uri 'none'; form-action 'none'

Wat dat betekent:

De beperking zit in de pagina zelf. Ook als er per ongeluk of met opzet code in zou zitten die iets wil versturen, houdt je browser dat tegen.

4De build narekenen: is de site gebouwd uit de broncode?

Onderaan de app staat gebouwd uit commit …: de commit waaruit de site is gebouwd. In sha256.json staat dezelfde commit, met van elk bestand de SHA-256 (een vingerafdruk van de inhoud).

De build doet bijna niets. Alle bestanden zijn precies gelijk aan die in de repository, op drie na:

De build is reproduceerbaar: dezelfde commit geeft byte voor byte dezelfde bestanden.

Voor wie de opdrachtregel kent

Nodig: Git en Python 3 (alleen de standaardbibliotheek).

git clone https://github.com/henriterhofte/betaal-qr
cd betaal-qr
python tools/controleer.py

Het script haalt sha256.json van de site en bouwt die commit zelf opnieuw. Het vergelijkt elk bestand met het manifest, downloadt daarna elk bestand van de site en controleert dat ook. Bij succes staat er: gelijk: wat op https://betaal.terhofte.net/ draait, is gebouwd uit commit …

Daarnaast draait er elke week een automatische controle die precies dit doet. Die faalt als de site afwijkt.

5De herkomst: is dit echt door GitHub gebouwd?

Bij elke uitrol legt GitHub Actions vast dat sha256.json is gebouwd uit deze repository en deze commit. Dat gebeurt met een attestatie (SLSA-herkomst, ondertekend via Sigstore).

Voor wie de opdrachtregel kent

Nodig: de GitHub CLI (gh).

curl -O https://betaal.terhofte.net/sha256.json
gh attestation verify sha256.json --repo henriterhofte/betaal-qr

Bij succes staat er welke workflow en welke commit het bestand hebben gebouwd.

6De bibliotheken: ongewijzigd

Betaal-QR gebruikt drie bibliotheken van anderen. Van elk staat een vaste kopie in de repository (vendor/), dus er wordt niets van een CDN geladen. Elk bestand is byte voor byte gelijk aan dat in de officiële npm-release:

bestandwaarvoorSHA-256
vendor/qrcode.jsqrcode-generator 1.4.4 (Kazuhiko Arase, MIT): de QR-code tekenen18ae399f81182bc9de916e9c77b195df20cc58d6f2d55a62b085a299f1bf1780
vendor/pdfjs/pdf.min.mjspdf.js 4.10.38 (Mozilla, Apache-2.0): een factuur-PDF lezen27fc2a057a00f92a4334ad06e17dbd7259912954e9fb7f76400bcca5fd190a9c
vendor/pdfjs/pdf.worker.min.mjs1baa1844c89c80a5b2797c916e75ab29254be46d8e9cb53cb6364d7aad84be36
vendor/tesseract/tesseract.esm.min.jsTesseract.js 5.1.1 en tesseract.js-core 5.1.1 (Apache-2.0): tekstherkenning in foto's en scans2537be686335e4b2637e933cdc85a52dd80267a592689c1bd63235c8591540ae
vendor/tesseract/worker.min.jsaca1229639fc9907d86f96e825955a2b7c5716d17f3bc3acd71f9c7ab66181fc
vendor/tesseract/core/tesseract-core-simd-lstm.wasm.jsce20eda9533cbed1e6c2b4276fbae1e0adc61b6754b5513084be601787b457cf
vendor/tesseract/core/tesseract-core-lstm.wasm.js8f04aa0cc81e7bde33f80e92fa01a7a665f0b4884d098acf5de9c7104a11dfaa
vendor/tesseract/lang/nld.traineddata.gzNederlands taalmodel (tessdata_best, integer; npm @tesseract.js-data/nld 1.0.0)a2d904b6ddc4feb0d31ecfcd7361a554102e7aa2e278c54f4fc029e0d0815571
vendor/jsqr/jsQR.jsjsQR 1.4.0 (Apache-2.0): een betaal-QR op de factuur lezenbc40c8a15196236b2314db0856f72ca0b49980cd5413b8c852a7349f5fee0859

pdf.js en jsQR worden pas geladen als je een factuur opent; Tesseract (± 7 MB) pas als je een foto of een gescande PDF opent. Daarna bewaart de service worker het, zodat het ook offline werkt. pdf.js draait met isEvalSupported: false en zonder webfonts, zodat het binnen de Content-Security-Policy blijft.

Zelf nagaan
curl -sL https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz | tar xzO package/qrcode.js | sha256sum
curl -sL https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz | tar xzO package/build/pdf.min.mjs | sha256sum
curl -sL https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz | tar xzO package/build/pdf.worker.min.mjs | sha256sum
curl -sL https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz | tar xzO package/dist/jsQR.js | sha256sum
curl -sL https://registry.npmjs.org/tesseract.js/-/tesseract.js-5.1.1.tgz | tar xzO package/dist/worker.min.js | sha256sum
curl -sL https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-5.1.1.tgz | tar xzO package/tesseract-core-simd-lstm.wasm.js | sha256sum
curl -sL https://registry.npmjs.org/@tesseract.js-data/nld/-/nld-1.0.0.tgz | tar xzO package/4.0.0_best_int/nld.traineddata.gz | sha256sum

7Wat dit niet dekt