#!/usr/bin/env node /* Stamp each given HTML page with today's date + its own MD5 and SHA-256. * * A file can't contain its own hash directly (writing the hash changes the * hash). So the hashes are computed over the page with the two hash fields * ZEROED, then the real values are written back (same length, so the file the * verifier hashes-after-zeroing is exactly what we hashed). Self-consistent and * idempotent. * * Verify a served page: sed the md5 field to 32 zeros and the sha256 field to * 64 zeros, then `md5sum` / `sha256sum` — must match the printed values. * * node web/stamp.js web/chat.html web/zebra-audio.html web/how-it-works.html */ const { readFileSync, writeFileSync } = require('fs'); const { createHash } = require('crypto'); const date = process.env.STAMP_DATE || new Date().toISOString().slice(0, 10); const Z32 = '0'.repeat(32), Z64 = '0'.repeat(64); let bad = 0; for (const f of process.argv.slice(2)) { let s = readFileSync(f, 'utf8'); if (!/class="stamp-md5"/.test(s)) { console.error(`! ${f}: no integrity footer, skipped`); bad++; continue; } s = s.replace(/()[^<]*(<\/span>)/, `$1${date}$2`); s = s.replace(/()[0-9a-fA-F]*(<\/span>)/, `$1${Z32}$2`) .replace(/()[0-9a-fA-F]*(<\/span>)/, `$1${Z64}$2`); const md5 = createHash('md5').update(s).digest('hex'); const sha = createHash('sha256').update(s).digest('hex'); s = s.replace(`${Z32}`, `${md5}`) .replace(`${Z64}`, `${sha}`); writeFileSync(f, s); console.log(`${f}\n date ${date}\n md5 ${md5}\n sha256 ${sha}`); } process.exit(bad ? 1 : 0);