web: stamp each zebra-report page with build date + md5/sha256
Add an integrity footer to chat.html, zebra-audio.html, and how-it-works.html showing the build date (2026-05-28) and the page's own MD5 + SHA-256. A file can't hold its own hash, so web/stamp.js (make stamp) computes the hashes with the two hash fields zeroed, then writes the real values back — self-consistent and idempotent. To verify a served page: blank the two fields and re-hash; confirmed it reproduces the stamped value with plain sha256sum.
This commit is contained in:
parent
60615a2e73
commit
ae3e4316fb
5 changed files with 58 additions and 1 deletions
6
Makefile
6
Makefile
|
|
@ -4,7 +4,11 @@ LDFLAGS = $(shell pkg-config --libs libpulse) -lrt -lpthread
|
|||
|
||||
PULSE = src/pulse.c
|
||||
|
||||
.PHONY: all clean serve blog test test-all test-web zebrad
|
||||
.PHONY: all clean serve blog test test-all test-web stamp zebrad
|
||||
|
||||
# stamp each web page with today's date + its own md5/sha256 (run before deploy)
|
||||
stamp:
|
||||
@node web/stamp.js web/chat.html web/zebra-audio.html web/how-it-works.html
|
||||
|
||||
all: tx rx chat bt carrier zebrad
|
||||
|
||||
|
|
|
|||
|
|
@ -1590,5 +1590,11 @@ logLine('sys', 'chat content lives in encrypted SRTP audio. no IP packets carry
|
|||
|
||||
})();
|
||||
</script>
|
||||
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span style="color:#777">page integrity</span> · built <span class="stamp-date">2026-05-28</span><br>
|
||||
md5 <span class="stamp-md5">2eefd0f7f0c5ca9b42078a885e2d822d</span><br>
|
||||
sha256 <span class="stamp-sha">20c008ca4c8341e9017f46488299120714599f4765e745820ab7e08d9b3b3306</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -225,5 +225,11 @@
|
|||
<a href="/" style="color:#777">unturf</a>
|
||||
</p>
|
||||
|
||||
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span style="color:#777">page integrity</span> · built <span class="stamp-date">2026-05-28</span><br>
|
||||
md5 <span class="stamp-md5">1309e6236d25cd129cb20580b6fc1171</span><br>
|
||||
sha256 <span class="stamp-sha">1561e3dc07925f0c3a92672d401c646ffd5621dcf6665f6d813873a51731a5c6</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
35
web/stamp.js
Normal file
35
web/stamp.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/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 class="stamp-date">)[^<]*(<\/span>)/, `$1${date}$2`);
|
||||
s = s.replace(/(<span class="stamp-md5">)[0-9a-fA-F]*(<\/span>)/, `$1${Z32}$2`)
|
||||
.replace(/(<span class="stamp-sha">)[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(`<span class="stamp-md5">${Z32}</span>`, `<span class="stamp-md5">${md5}</span>`)
|
||||
.replace(`<span class="stamp-sha">${Z64}</span>`, `<span class="stamp-sha">${sha}</span>`);
|
||||
writeFileSync(f, s);
|
||||
console.log(`${f}\n date ${date}\n md5 ${md5}\n sha256 ${sha}`);
|
||||
}
|
||||
process.exit(bad ? 1 : 0);
|
||||
|
|
@ -395,5 +395,11 @@ $('music-mode').addEventListener('change', async (e)=>{
|
|||
logLine('', 'ready — type a rendezvous code and call. mic stays muted to the room until connected.');
|
||||
})();
|
||||
</script>
|
||||
<footer style="max-width:560px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span style="color:#777">page integrity</span> · built <span class="stamp-date">2026-05-28</span><br>
|
||||
md5 <span class="stamp-md5">e5fddae2c65939dee70fa8cb05f246d0</span><br>
|
||||
sha256 <span class="stamp-sha">ca3d20d2d9d8f33ce23faee41a148e459ef404e6a4be73001cd991781ce146bc</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue