oxedyne/daimond/verify/ext/check.js
3.3 KiB, 1 run
created by r2519314175:1011, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // verify/ext/check.js — verify a served Daimond origin, from the extension. |
| 2 | // |
| 3 | // The extension fetches the served manifest and every file it lists FROM THE |
| 4 | // SITE, and the transparency log from GitHub — a different origin the site does |
| 5 | // not control. Because this code is installed, not served, a tampered server |
| 6 | // cannot tamper with the check itself. That is what makes the extension the |
| 7 | // trustworthy, always-on form of "check the code your browser is running". |
| 8 | |
| 9 | import * as fp from './fingerprint.js'; |
| 10 | |
| 11 | /// The public log, on an origin the checked site does not control. Overridable |
| 12 | /// (chrome.storage `logUrl`) so a fork can point at its own, and so tests can |
| 13 | /// serve a local copy. |
| 14 | export const DEFAULT_LOG = |
| 15 | 'https://raw.githubusercontent.com/oxedyne-com/daimond/main/verify/transparency.jsonl'; |
| 16 | |
| 17 | /// Verify the build served at `origin`. Returns |
| 18 | /// { ok, failed, build, bundle, checks: [{ name, ok, detail }] } |
| 19 | /// where a check's `ok` is true/false, or null when it could not run (offline). |
| 20 | export async function verifyOrigin(origin, logUrl) { |
| 21 | logUrl = logUrl || DEFAULT_LOG; |
| 22 | const checks = []; |
| 23 | const add = (name, ok, detail) => checks.push({ name, ok, detail: detail || '' }); |
| 24 | |
| 25 | let manifest; |
| 26 | try { |
| 27 | manifest = await (await fetch(origin + '/manifest.json', { cache: 'no-store' })).json(); |
| 28 | } catch (e) { |
| 29 | add('manifest', false, 'this site served no manifest.json — it cannot be checked'); |
| 30 | return verdict(checks, null); |
| 31 | } |
| 32 | |
| 33 | // The manifest's bundle hash is the hash of its own file list. |
| 34 | add('manifest self-consistent', (await fp.bundleHash(manifest.files)) === manifest.bundle, |
| 35 | 'the bundle hash matches the file list'); |
| 36 | |
| 37 | // The one that counts: the served bundle is a sealed entry in the public, |
| 38 | // hash-chained log — fetched from GitHub, which this site does not control. |
| 39 | try { |
| 40 | const text = await (await fetch(logUrl, { cache: 'no-store' })).text(); |
| 41 | const entries = text.split('\n').map(l => l.trim()).filter(Boolean).map(JSON.parse); |
| 42 | const chain = await fp.verifyChain(entries); |
| 43 | if (!chain.ok) { |
| 44 | add('public transparency log', false, 'the public log is not an intact chain: ' + chain.error); |
| 45 | } else { |
| 46 | const sealed = entries.some(e => e.bundle === manifest.bundle); |
| 47 | add('sealed in the public log', sealed, |
| 48 | sealed ? entries.length + ' releases on record' |
| 49 | : 'this served bundle was never published'); |
| 50 | } |
| 51 | } catch (e) { |
| 52 | add('public transparency log', null, 'could not reach the public log (offline?)'); |
| 53 | } |
| 54 | |
| 55 | // Every served file hashes to what the manifest says. |
| 56 | const bad = []; |
| 57 | for (const rel of Object.keys(manifest.files)) { |
| 58 | try { |
| 59 | const res = await fetch(origin + '/' + rel, { cache: 'no-store' }); |
| 60 | const got = await fp.sha256(new Uint8Array(await res.arrayBuffer())); |
| 61 | if (got !== manifest.files[rel]) bad.push(rel); |
| 62 | } catch (e) { bad.push(rel + ' (unreadable)'); } |
| 63 | } |
| 64 | add('every served file matches the manifest', bad.length === 0, |
| 65 | bad.length ? bad.length + ' differ: ' + bad.slice(0, 6).join(', ') |
| 66 | : Object.keys(manifest.files).length + ' files'); |
| 67 | |
| 68 | return verdict(checks, manifest); |
| 69 | } |
| 70 | |
| 71 | function verdict(checks, manifest) { |
| 72 | const failed = checks.some(c => c.ok === false); |
| 73 | const unknown = checks.some(c => c.ok === null); |
| 74 | return { |
| 75 | ok: !failed && !unknown, |
| 76 | failed: failed, |
| 77 | build: manifest ? manifest.build : '', |
| 78 | bundle: manifest ? manifest.bundle : '', |
| 79 | checks: checks, |
| 80 | }; |
| 81 | } |