Oregami
Repositories/oxedyne/daimond

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
9import * 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.
14export 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).
20export 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
71function 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}