Snapshot of the decentralized-web stack at the point of the resolver+Theseus rebuild deploy. Includes: - Argus (BNS engine + resolver daemon + Sia gateway) - AriadneResolver (Windows Inno installer bundle + Android APK sources + Firefox extension) - TheseusNavigator (Electron browser) - site/ (silentmode.st content, deployed to Sia at bns/silentmode/) - design docs, roadmap, protocol spec Secrets excluded via .gitignore: Argus/sia-s3.json, Argus/wallets.json, Argus/ca/*.key,*.crt. Build outputs, node_modules, and bundled runtimes also excluded. Shipped hashes on dl.silentmode.st at this commit: AriadneResolver-Setup-0.1.0.exe 5bcb216eef31ea28ed767e4134ab74bd5ac69dfbd365fd249e9e6938e55c986a TheseusNavigator-Setup-0.0.1.exe 7c735e88bad2da3347145adba3016c8f626a18b8422289c8c6ba471972e2952b TheseusNavigator-0.0.1-portable.exe 008fd84445babeabb401b2bca40ea9466b24b0e6d6c85104da7640c5c5c84521 ariadne-v0.2.apk 635c8f04d44ef855a8390b9eeddb8cd2d50622e81cc4e5004e8daffc1bb0425c
84 lines
3.9 KiB
JavaScript
84 lines
3.9 KiB
JavaScript
// The TLDs this installation will mint TLS certificates for.
|
|
//
|
|
// The BNS protocol is deliberately TLD-agnostic: bns.js accepts any
|
|
// /^[a-z0-9-]{1,16}$/ as a TLD, because the chain decides what exists, not this
|
|
// file. TLS is the one place that cannot stay open-ended. The local root CA is
|
|
// installed into the machine trust store, and an X.509 name-constrained CA needs
|
|
// a *finite* list of permitted dNSName subtrees (RFC 5280 §4.2.1.10). Without
|
|
// one the root can vouch for every domain on the internet.
|
|
//
|
|
// So: resolution stays unbounded, certificate issuance is enumerated. A name on
|
|
// a TLD that is not listed here still resolves and still serves over plain HTTP
|
|
// — it just gets no certificate from the local CA.
|
|
//
|
|
// Adding a TLD requires regenerating the root CA and re-importing it on every
|
|
// machine (see setup/install.ps1). That cost is the point: it is what stops this
|
|
// root from being a general-purpose signing oracle.
|
|
|
|
import { readFileSync, existsSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
// RFC 2606 reserves `.test` forever, and none of the others are ICANN-delegated.
|
|
export const DEFAULT_TLDS = ["bch", "p2p", "bit", "deviant", "nav", "test", "bcnr"];
|
|
|
|
// A tripwire, not a complete list — ICANN has ~1500 delegated TLDs and adds more.
|
|
// Its job is to make the obvious mistake loud, not to be authoritative. Anything
|
|
// added to the permitted list must be checked against the real IANA root zone:
|
|
// https://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
|
const ICANN_TRIPWIRE = new Set([
|
|
"com", "net", "org", "edu", "gov", "mil", "int", "info", "biz", "io", "co",
|
|
"app", "dev", "xyz", "online", "site", "cloud", "ai", "me", "tv", "sh", "gg",
|
|
"uk", "us", "de", "fr", "nl", "eu", "ru", "cn", "jp", "ca", "au", "ch", "it",
|
|
]);
|
|
|
|
const TLD_RE = /^[a-z0-9-]{1,16}$/;
|
|
const CONFIG_FILE = fileURLToPath(new URL("../../ariadne.config.json", import.meta.url));
|
|
|
|
function validate(tlds) {
|
|
const out = [];
|
|
for (const raw of tlds) {
|
|
const tld = String(raw).trim().toLowerCase().replace(/^\.+/, "");
|
|
if (!TLD_RE.test(tld)) throw new Error(`invalid TLD "${raw}" in permitted list (allowed: [a-z0-9-]{1,16})`);
|
|
if (ICANN_TRIPWIRE.has(tld)) {
|
|
throw new Error(
|
|
`refusing to permit ".${tld}" — it is an ICANN-delegated TLD. Adding it would let the ` +
|
|
`local root CA issue trusted certificates for real internet domains on every machine ` +
|
|
`that installed it. If this is genuinely intended, edit ICANN_TRIPWIRE in src/lib/tlds.js.`,
|
|
);
|
|
}
|
|
if (!out.includes(tld)) out.push(tld);
|
|
}
|
|
if (!out.length) throw new Error("permitted TLD list is empty — the CA could issue nothing");
|
|
return out;
|
|
}
|
|
|
|
// Effective list = ariadne.config.json "tlds", else the built-in default.
|
|
// Read once at import: the root CA is baked against this list, so changing it at
|
|
// runtime would silently desync the constraints from the certificates.
|
|
export const PERMITTED_TLDS = (() => {
|
|
try {
|
|
if (existsSync(CONFIG_FILE)) {
|
|
const cfg = JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
if (Array.isArray(cfg.tlds) && cfg.tlds.length) return validate(cfg.tlds);
|
|
}
|
|
} catch (e) {
|
|
if (e.message.startsWith("refusing") || e.message.startsWith("invalid TLD")) throw e;
|
|
console.error(`tlds: ${CONFIG_FILE} unreadable (${e.message}) — using defaults`);
|
|
}
|
|
return validate(DEFAULT_TLDS);
|
|
})();
|
|
|
|
// Rightmost label of a hostname, minus any port or trailing dot. null if unusable.
|
|
export function hostTld(host) {
|
|
if (typeof host !== "string" || !host) return null;
|
|
const bare = host.trim().toLowerCase().replace(/\.$/, "").replace(/:\d+$/, "");
|
|
if (!bare || bare.includes("/")) return null;
|
|
const tld = bare.slice(bare.lastIndexOf(".") + 1);
|
|
return TLD_RE.test(tld) ? tld : null;
|
|
}
|
|
|
|
// May the local CA issue a certificate for this hostname?
|
|
export function isPermittedHost(host, tlds = PERMITTED_TLDS) {
|
|
const tld = hostTld(host);
|
|
return tld !== null && tlds.includes(tld);
|
|
}
|