From 5ffab449783c32547b116353e8f658d805978121 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Wed, 16 Sep 2026 16:47:20 +0000 Subject: [PATCH] Ship the Moshpit Root CA: fetched on install and upgrade, trusted by the launcher on every start The registry now signs a certificate for every name it holds (moshcoder/moshcode#519), so one root makes every Moshpit name trusted over https. install.sh ensure_moshpit_root fetches /api/moshpit/ca and /api/moshpit/ca.crt on install and on tron upgrade, refuses a root whose fingerprint does not match what the registry reports or that is not CA:TRUE, and keeps it next to the launcher as moshpit-root-ca.crt. The launcher's sync_moshpit_trust imports it into every browser trust store it writes under the nickname Moshpit Root CA, the one moshcode dns enable uses, so neither imports the other's work twice. certutil is now installed on machines that have the shipped root, not only ones with moshcode's certificates. Verified against the live registry: fetch, no-op on rerun, skip switch, a tampered answer refused, and the launcher importing it as C,,. Co-Authored-By: Claude Fable 5.1 --- apps/desktop/launcher/tronbrowser | 14 +++++++--- apps/web/public/install.sh | 43 +++++++++++++++++++++++++++++++ docs/moshpit-pit-toggle.md | 11 ++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/apps/desktop/launcher/tronbrowser b/apps/desktop/launcher/tronbrowser index 3bd984a..9a395b0 100755 --- a/apps/desktop/launcher/tronbrowser +++ b/apps/desktop/launcher/tronbrowser @@ -591,6 +591,7 @@ fi # it already wrote is recognised as done rather than imported a second time. moshpit_nickname() { # cert_file case "$1" in + */moshpit-root-ca.crt|*/.moshpit/ca/registry-root.crt) printf 'Moshpit Root CA' ;; */moshpit-local-ca.crt|*/.moshpit/ca/ca.crt) printf 'Moshpit Local CA' ;; *) _base="$(basename "$1" .crt)" @@ -630,7 +631,12 @@ sync_moshpit_trust() { # Each word below is a literal path or a glob result, so this stays # whitespace-safe; an unmatched glob arrives as the pattern itself and fails # the -f test. - for _cert in /usr/local/share/ca-certificates/moshpit-*.crt \ + # The registry's root first: the one TronBrowser ships itself (install.sh + # ensure_moshpit_root), which makes every Moshpit name trusted at once. The + # rest is whatever `moshcode dns enable` / `dns trust` installed system-wide. + for _cert in "$DIR/moshpit-root-ca.crt" \ + "$HOME/.moshpit/ca/registry-root.crt" \ + /usr/local/share/ca-certificates/moshpit-*.crt \ /etc/ca-certificates/trust-source/anchors/moshpit-*.crt \ /etc/pki/ca-trust/source/anchors/moshpit-*.crt \ "$HOME/.moshpit/ca/ca.crt"; do @@ -662,9 +668,9 @@ sync_moshpit_trust() { _flag="C,," fi else - # No openssl: the Local CA is the only anchor Moshpit ships, and it is the - # one file we can identify by name alone. - case "$_nick" in "Moshpit Local CA") _flag="C,," ;; esac + # No openssl: the two CAs are the only anchors Moshpit ships, and they + # are the files we can identify by name alone. + case "$_nick" in "Moshpit Local CA"|"Moshpit Root CA") _flag="C,," ;; esac fi if certutil -d "sql:$_nssdb" -A -t "$_flag" -n "$_nick" -i "$_cert" >/dev/null 2>&1; then diff --git a/apps/web/public/install.sh b/apps/web/public/install.sh index 5eac690..4eaf5b6 100755 --- a/apps/web/public/install.sh +++ b/apps/web/public/install.sh @@ -811,6 +811,42 @@ ensure_engine() { return 1 } +# The Moshpit registry runs a certificate authority for the names it holds +# (moshcode apps/pwa/docs/moshpit-ca.md). One root, installed once, and every +# Moshpit name is trusted over https — no per-name imports, no pins to check. +# Fetched here on install and on every upgrade, kept next to the launcher as +# moshpit-root-ca.crt, and the launcher imports it into the browser's trust +# store on every start (sync_moshpit_trust). Two fetches that must agree: the +# fingerprint the registry reports for its root, and the root itself. Skip +# with TB_NO_MOSHPIT_ROOT=1; another registry with TRONBROWSER_MOSHPIT_REGISTRY. +MOSHPIT_REGISTRY="${TRONBROWSER_MOSHPIT_REGISTRY:-https://pit.moshcode.sh}" + +ensure_moshpit_root() { + [ "${TB_NO_MOSHPIT_ROOT:-0}" = "1" ] && return 0 + _ldir="$(find "$APP_DIR" -maxdepth 3 -type f -name tronbrowser 2>/dev/null | head -n1)" + [ -n "$_ldir" ] || return 0 + dest="$(dirname "$_ldir")/moshpit-root-ca.crt" + tmp="$(mktemp -d)" + status="$(curl -fsSL --max-time 15 "$MOSHPIT_REGISTRY/api/moshpit/ca" 2>/dev/null || true)" + case "$status" in + *'"enabled":true'*) ;; + *) rm -rf "$tmp"; return 0 ;; # no CA published: nothing to ship, nothing to say + esac + want="$(printf '%s' "$status" | sed -n 's/.*"fingerprint_sha256":"\([^"]*\)".*/\1/p' | tr -d ':' | tr 'a-f' 'A-F')" + if ! fetch "$MOSHPIT_REGISTRY/api/moshpit/ca.crt" "$tmp/root.crt" 2>/dev/null; then rm -rf "$tmp"; return 0; fi + if command -v openssl >/dev/null 2>&1; then + got="$(openssl x509 -in "$tmp/root.crt" -noout -fingerprint -sha256 2>/dev/null | sed 's/.*=//' | tr -d ':' | tr 'a-f' 'A-F')" + if [ -z "$got" ] || [ -z "$want" ] || [ "$got" != "$want" ]; then + warn "The Moshpit root the registry served does not match the fingerprint it reports; not installing it." + rm -rf "$tmp"; return 1 + fi + openssl x509 -in "$tmp/root.crt" -noout -ext basicConstraints 2>/dev/null | grep -q 'CA:TRUE' || { rm -rf "$tmp"; return 1; } + fi + if [ -f "$dest" ] && cmp -s "$tmp/root.crt" "$dest"; then rm -rf "$tmp"; return 0; fi + install -m 0644 "$tmp/root.crt" "$dest" && info "Installed the Moshpit Root CA (every Moshpit name over https) to $dest" + rm -rf "$tmp" +} + ensure_obscura() { [ "${TB_NO_OBSCURA_INSTALL:-0}" = "1" ] && return 0 obdest="$APP_DIR/obscura-bin" @@ -894,6 +930,10 @@ ensure_certutil() { "$HOME/.moshpit/ca/ca.crt"; do if [ -f "$_c" ]; then _have_moshpit=1; break; fi done + # Or the root TronBrowser ships itself (ensure_moshpit_root), which is the + # ordinary case now that the registry signs. + _ldir2="$(find "$APP_DIR" -maxdepth 3 -type f -name tronbrowser 2>/dev/null | head -n1)" + [ -n "$_ldir2" ] && [ -f "$(dirname "$_ldir2")/moshpit-root-ca.crt" ] && _have_moshpit=1 [ "$_have_moshpit" = "1" ] || return 0 info "Setting up certutil (so Moshpit names load over HTTPS)…" @@ -971,6 +1011,7 @@ DESKTOP # abort an install that has already put the browser on disk. ensure_tor || true # so the in-browser 🧅 Tor toggle works out of the box ensure_certutil || true # so Moshpit names load over HTTPS on first launch + ensure_moshpit_root || true # the registry's root: every Moshpit name over https ensure_obscura || true # so 'tron automate' has its scraping engine brand_macos_icon "$(dirname "$bin")/tronbrowser.png" @@ -1080,6 +1121,7 @@ do_upgrade() { ensure_browser # still make sure Ungoogled Chromium is installed ensure_tor || true # and that Tor is available for the toggle ensure_certutil || true # and that Moshpit trust can be written + ensure_moshpit_root || true # and that the registry's root is current ensure_obscura || true # and that the scraping engine is current brand_macos_icon "$(find "$APP_DIR" -maxdepth 3 -name tronbrowser.png 2>/dev/null | head -n1)" # re-apply icon (Chromium updates reset it) info "Re-install anyway with: TB_FORCE=1 tron upgrade" @@ -1143,6 +1185,7 @@ case "$cmd" in remove|uninstall) do_remove ;; ensure-tor) ensure_tor ;; ensure-engine) ensure_engine ;; + ensure-moshpit-root) ensure_moshpit_root ;; ensure-obscura) ensure_obscura ;; ensure-certutil) ensure_certutil ;; version|--version|-v) do_version ;; diff --git a/docs/moshpit-pit-toggle.md b/docs/moshpit-pit-toggle.md index fc704d4..e95f787 100644 --- a/docs/moshpit-pit-toggle.md +++ b/docs/moshpit-pit-toggle.md @@ -59,6 +59,17 @@ The PAC is not `mandatory`: if it ever fails to evaluate, Chromium falls back to ## HTTPS on a pit name +**Since 2026-09-16 the registry signs.** pit.moshcode.sh runs a certificate +authority for the names it holds (moshcode `apps/pwa/docs/moshpit-ca.md`): +one root, 30-day leaves per name issued to whoever controls the name. The +installer fetches that root on install and on `tron upgrade` +(`ensure_moshpit_root`, checked against the fingerprint the registry reports), +keeps it next to the launcher as `moshpit-root-ca.crt`, and the launcher +imports it into the browser's trust store on every start under the nickname +`Moshpit Root CA`, the same one `moshcode dns enable` uses. Once origins serve +registry-signed chains, that root is all a browser needs; the per-name import +below stays for origins that still self-sign and is otherwise idle. + No public CA issues for a name outside the ICANN root, so an origin such as `chovy.hacker` serves a self-signed leaf for its own name and the registry publishes the SHA-256 of that key (`/api/moshpit/pins?name=`, the RFC 7469 pin