diff --git a/apps/desktop/launcher/tronbrowser b/apps/desktop/launcher/tronbrowser index 2f831fc..697c538 100755 --- a/apps/desktop/launcher/tronbrowser +++ b/apps/desktop/launcher/tronbrowser @@ -462,6 +462,102 @@ if [ "$TOR" != "1" ] && [ -z "$MAC_APP" ] && [ -f "$DIR/tron-pwa" ]; then fi fi +# --- Moshpit names over HTTPS ---------------------------------------------- +# Chromium does not read /etc/ssl/certs. On Linux the only user-added trust it +# honours lives in the per-user NSS database at ~/.pki/nssdb — a path keyed off +# $HOME, not off --user-data-dir, so no TronBrowser profile can carry it and no +# amount of reinstalling puts it there. +# +# Moshpit installs its certificates into the OpenSSL store instead. That is why +# `curl https://chovy.hacker` returns the page while the same URL here fails +# with ERR_CERT_AUTHORITY_INVALID: the two are reading different stores. It +# reads as a TronBrowser bug, and there is nothing the user can do about it from +# inside the browser, so mirror whatever Moshpit installed into the store +# Chromium actually reads. +# +# Two certificate shapes, and the trust flag differs: +# * the Moshpit Local CA (CA:TRUE) — an anchor, "C,,". This is what a machine +# behind the pinned-TLS proxy is served. +# * a per-name origin leaf (CA:FALSE, self-signed, SAN = the name) — "P,,", +# a trusted peer. Importing one as "C,," fails: Chromium will not anchor a +# CA:FALSE certificate. Reading basicConstraints beats guessing from the +# filename, because getting this backwards fails exactly like doing nothing. +# +# Runs on every start rather than once at install. Names get pointed and trusted +# long after the browser is installed, and re-importing one already in the +# database is skipped by nickname, so the steady state costs a `certutil -L`. +# Set TRONBROWSER_NO_MOSHPIT_TRUST=1 to opt out entirely. +# +# The Local CA keeps the nickname `moshcode dns enable` gives it, so a database +# it already wrote is recognised as done rather than imported a second time. +moshpit_nickname() { # cert_file + case "$1" in + */moshpit-local-ca.crt|*/.moshpit/ca/ca.crt) printf 'Moshpit Local CA' ;; + *) + _base="$(basename "$1" .crt)" + printf 'moshpit %s' "${_base#moshpit-}" + ;; + esac +} + +sync_moshpit_trust() { + if [ "$(uname -s)" != "Linux" ]; then return 0; fi + if [ "${TRONBROWSER_NO_MOSHPIT_TRUST:-0}" = "1" ]; then return 0; fi + + _nssdb="$HOME/.pki/nssdb" + _ready=0 + + # 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 \ + /etc/ca-certificates/trust-source/anchors/moshpit-*.crt \ + /etc/pki/ca-trust/source/anchors/moshpit-*.crt \ + "$HOME/.moshpit/ca/ca.crt"; do + if [ ! -f "$_cert" ]; then continue; fi + + # Deferred until we know there is something to import: a machine that never + # heard of Moshpit should not be told to go install anything. + if [ "$_ready" = "0" ]; then + if ! command -v certutil >/dev/null 2>&1; then + echo "TronBrowser: Moshpit certificates are installed, but 'certutil' is not." >&2 + echo " Chromium reads its own trust store, so .hacker/.rank names fail TLS here." >&2 + echo " Debian/Ubuntu: sudo apt install libnss3-tools Fedora: sudo dnf install nss-tools" >&2 + echo " Arch: sudo pacman -S nss Then start TronBrowser again." >&2 + return 0 + fi + if [ ! -f "$_nssdb/cert9.db" ]; then + mkdir -p "$_nssdb" 2>/dev/null || return 0 + certutil -d "sql:$_nssdb" -N --empty-password >/dev/null 2>&1 || return 0 + fi + _ready=1 + fi + + _nick="$(moshpit_nickname "$_cert")" + if certutil -d "sql:$_nssdb" -L -n "$_nick" >/dev/null 2>&1; then continue; fi + + _flag="P,," + if command -v openssl >/dev/null 2>&1; then + if openssl x509 -noout -text -in "$_cert" 2>/dev/null | grep -q 'CA:TRUE'; then + _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 + fi + + if certutil -d "sql:$_nssdb" -A -t "$_flag" -n "$_nick" -i "$_cert" >/dev/null 2>&1; then + echo "TronBrowser: trusted $_nick for Moshpit HTTPS." >&2 + fi + done + return 0 +} + +# Best-effort and never fatal: a trust store we could not write must not stop +# the browser from opening. +sync_moshpit_trust || true + # --- GPU backend ----------------------------------------------------------- # A crashing GPU process does not look like a crash. The window stays up and the # page keeps whatever it had already rasterized — a logo, a header — while diff --git a/apps/web/public/install.sh b/apps/web/public/install.sh index 645976d..fc30847 100755 --- a/apps/web/public/install.sh +++ b/apps/web/public/install.sh @@ -642,6 +642,51 @@ ensure_tor() { return 1 } +# Chromium takes user-added trust only from the per-user NSS database at +# ~/.pki/nssdb, never from /etc/ssl/certs. The launcher mirrors Moshpit's +# certificates into it on every start so `.hacker` / `.rank` names load over +# HTTPS — but writing an NSS database needs `certutil`, and that is the one part +# of the job that needs a package installed. Doing it here is the difference +# between the launcher fixing the problem and the launcher printing a command +# for the user to run, which is the manual step this is meant to remove. +# +# Only on a machine that actually has Moshpit certificates: a package nobody +# needs is not ours to install. Best-effort throughout — the launcher explains +# what to do if this cannot get there. +ensure_certutil() { + # macOS Chromium reads the system keychain, which `moshcode dns enable` + # already writes. There is no NSS database in the picture at all. + [ "$(uname -s)" = "Linux" ] || return 0 + command -v certutil >/dev/null 2>&1 && return 0 + + _have_moshpit=0 + for _c in /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 + if [ -f "$_c" ]; then _have_moshpit=1; break; fi + done + [ "$_have_moshpit" = "1" ] || return 0 + + info "Setting up certutil (so Moshpit names load over HTTPS)…" + uid="$(id -u 2>/dev/null || echo 0)" + SUDO="" + if [ "$uid" -ne 0 ] && command -v sudo >/dev/null 2>&1 && { [ -t 1 ] || [ -t 2 ]; }; then SUDO="sudo"; fi + if [ "$uid" -eq 0 ] || [ -n "$SUDO" ]; then + # The tool is the same everywhere; only the package carrying it differs. + if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update -y >/dev/null 2>&1; $SUDO apt-get install -y libnss3-tools >/dev/null 2>&1 || true + elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y nss-tools >/dev/null 2>&1 || true + elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y nss-tools >/dev/null 2>&1 || true + elif command -v pacman >/dev/null 2>&1; then $SUDO pacman -Sy --noconfirm nss >/dev/null 2>&1 || true + elif command -v zypper >/dev/null 2>&1; then $SUDO zypper --non-interactive install mozilla-nss-tools >/dev/null 2>&1 || true + elif command -v apk >/dev/null 2>&1; then $SUDO apk add nss-tools >/dev/null 2>&1 || true + fi + command -v certutil >/dev/null 2>&1 && return 0 + fi + warn "Couldn't install certutil automatically. Moshpit names (.hacker, .rank) will fail TLS in TronBrowser until it is installed (e.g. 'sudo apt install libnss3-tools' / 'sudo pacman -S nss')." + return 1 +} + do_install() { need uname asset="$(detect_asset)" @@ -692,7 +737,11 @@ DESKTOP command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps_dir" 2>/dev/null || true ensure_browser - ensure_tor # so the in-browser 🧅 Tor toggle works out of the box + # `|| true` because this script runs under `set -eu`: both of these return 1 + # when they could not install their tool, and a missing nice-to-have must not + # 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 brand_macos_icon "$(dirname "$bin")/tronbrowser.png" info "Installed TronBrowser $tag to $APP_DIR" @@ -791,8 +840,9 @@ do_upgrade() { [ -n "$latest" ] || err "could not resolve the latest release of $REPO" if [ "$current" = "$latest" ] && [ "${TB_FORCE:-0}" != "1" ]; then info "TronBrowser is already up to date ($current)." - ensure_browser # still make sure Ungoogled Chromium is installed - ensure_tor # and that Tor is available for the toggle + 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 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" return @@ -851,6 +901,7 @@ case "$cmd" in esac ;; remove|uninstall) do_remove ;; ensure-tor) ensure_tor ;; + ensure-certutil) ensure_certutil ;; version|--version|-v) do_version ;; help|--help|-h) usage ;; *) err "unknown command: $cmd (try 'help')" ;;