Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/desktop/extensions/ai-sidebar/sidepanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,10 @@ async function togglePit() {
const trust = res && res.trust;
const httpsTip = !trust
? ''
: trust.available
? '<code>https://</code> on a pit name is trusted per name on first use, when the registry publishes its pin.'
: trust.available && trust.relaunch
? '<code>https://</code> on a pit name: the first visit records its key (when the registry publishes a matching pin), and it loads after the next TronBrowser restart — this engine only takes pins at start.'
: trust.available
? '<code>https://</code> on a pit name is trusted per name on first use, when the registry publishes its pin.'
: trust.why === 'no-certutil'
? '<code>https://</code> on a pit name will warn until <code>certutil</code> is installed (Debian/Ubuntu: <code>libnss3-tools</code>, Fedora: <code>nss-tools</code>, Arch: <code>nss</code>).'
: '<code>https://</code> on a pit name will warn on this platform; run <code>moshcode dns enable</code> for the certificate.';
Expand Down
88 changes: 67 additions & 21 deletions apps/desktop/launcher/tron-tor-helper
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ BUNDLED_DIR = os.environ.get("TRON_TOR_BIN_DIR", "")
PIDFILE = os.environ.get("TRON_TOR_PIDFILE", "")
# Bumped whenever the helper protocol/behaviour changes; the launcher kills a
# stale helper so the current version always runs.
HELPER_VERSION = "3.4.1"
HELPER_VERSION = "3.4.2"
_lock = threading.Lock()
_proc = None # the running tor subprocess (or None)
_ready = False # True once tor reported Bootstrapped 100%
Expand Down Expand Up @@ -347,13 +347,28 @@ def pit_resolve(name):
# handshake that follows already finds the certificate trusted.
PIT_REGISTRY = os.environ.get("TRON_PIT_REGISTRY", "https://pit.moshcode.sh").rstrip("/")
PIT_NSSDB = os.environ.get("TRON_PIT_NSSDB", os.path.expanduser("~/.pki/nssdb"))
# The launcher names the database of the engine it actually started (colon
# separated). A Flatpak Chromium is sandboxed with `--persist=.pki`: inside it,
# ~/.pki IS ~/.var/app/<app>/.pki, so an import into the real ~/.pki/nssdb is
# invisible to it. Every Chromium-looking Flatpak database that exists is
# covered as well, so the import lands wherever the browser will look.
# Chromium opens ONE of two databases per home: the legacy ~/.pki/nssdb, or
# since M146 ${XDG_DATA_HOME:-~/.local/share}/pki/nssdb — and which one a given
# build picks has changed between versions (bonita's Flatpak 152 read the XDG
# one while ~/.pki/nssdb existed beside it). A Flatpak Chromium adds a twist:
# it is sandboxed with `--persist=.pki` and XDG_DATA_HOME=~/.var/app/<app>/data,
# so its two candidates are ~/.var/app/<app>/.pki/nssdb and
# ~/.var/app/<app>/data/pki/nssdb, and the real ~/.pki is invisible to it.
# Rule: for each home, write every candidate that already exists; create the
# legacy one only when neither does (creating ~/.pki/nssdb next to a populated
# XDG database would flip a newer Chromium onto an empty store). The launcher
# names the engine's own candidates in TRON_PIT_NSSDB_EXTRA (colon separated).
PIT_NSSDB_EXTRA = os.environ.get("TRON_PIT_NSSDB_EXTRA", "")
# What the launcher started: "flatpak" or "native". The Flathub ungoogled-chromium
# ignores NSS user trust (verified on bonita: the leaf sat in the very database
# strace showed it opening, and Chromium still said "No matching issuer found"),
# but it honours --ignore-certificate-errors-spki-list. So every pin this helper
# accepts is also written to PIT_PINS_FILE, and for a Flatpak engine the launcher
# passes those pins on the command line at the next start. A name first trusted
# mid-session therefore needs one relaunch on a Flatpak engine; the sidebar says so.
PIT_ENGINE = os.environ.get("TRON_PIT_ENGINE", "native")
PIT_CERT_DIR = os.environ.get("TRON_PIT_CERT_DIR", os.path.expanduser("~/.tronbrowser/pit-certs"))
PIT_PINS_FILE = os.environ.get("TRON_PIT_PINS_FILE", os.path.join(PIT_CERT_DIR, "pins.txt"))
_trust_lock = threading.Lock()
_trust_seen = {} # name -> (ok, why); retried after a failure only once the pit restarts

Expand Down Expand Up @@ -435,28 +450,58 @@ def _safe_name(name):
return re.sub(r"\.{2,}", ".", re.sub(r"[^a-z0-9.-]", "", name.lower())).strip(".-")


def remember_pin(name, pin):
"""Append `name pin` to the pins file the launcher reads (once per pair)."""
try:
os.makedirs(os.path.dirname(PIT_PINS_FILE), mode=0o700, exist_ok=True)
have = set()
if os.path.exists(PIT_PINS_FILE):
with open(PIT_PINS_FILE) as f:
have = {line.strip() for line in f}
line = "%s %s" % (name, pin)
if line not in have:
with open(PIT_PINS_FILE, "a") as f:
f.write(line + "\n")
except OSError as exc:
log("pit: could not record the pin for %s: %s" % (name, exc))


def trust_available():
"""Can this machine take a per-name import at all? {available, why}."""
"""Can this machine take a per-name import at all? {available, why}.
`relaunch` is true where the engine only honours pins given at start."""
if platform.system() != "Linux":
return {"available": False, "why": "unsupported-platform"}
return {"available": False, "why": "unsupported-platform", "engine": PIT_ENGINE}
if not shutil.which("certutil"):
return {"available": False, "why": "no-certutil"}
return {"available": True, "why": "certutil", "nssdbs": pit_nssdbs()}
return {"available": False, "why": "no-certutil", "engine": PIT_ENGINE}
return {"available": True, "why": "certutil", "engine": PIT_ENGINE,
"relaunch": PIT_ENGINE == "flatpak", "nssdbs": pit_nssdbs()}


def _nssdb_groups():
"""[[candidate, ...], ...] — one group per home a Chromium might use."""
xdg = os.environ.get("XDG_DATA_HOME") or os.path.expanduser("~/.local/share")
groups = [[PIT_NSSDB, os.path.join(xdg, "pki", "nssdb")]]
extra = [d for d in PIT_NSSDB_EXTRA.split(":") if d]
if extra:
groups.append(extra)
for appdir in sorted(glob.glob(os.path.expanduser("~/.var/app/*"))):
if "chromium" in os.path.basename(appdir).lower() and os.path.isdir(appdir):
groups.append([os.path.join(appdir, ".pki", "nssdb"),
os.path.join(appdir, "data", "pki", "nssdb")])
return groups


def pit_nssdbs():
"""Every NSS database a Chromium on this machine might read, primary first."""
dbs = [PIT_NSSDB] + [d for d in PIT_NSSDB_EXTRA.split(":") if d]
for cand in sorted(glob.glob(os.path.expanduser("~/.var/app/*/.pki/nssdb"))):
app = os.path.basename(os.path.dirname(os.path.dirname(cand)))
if "chromium" in app.lower():
dbs.append(cand)
"""Every NSS database a Chromium on this machine might read: per home, the
candidates that already exist, else the first one (to be created)."""
seen, out = set(), []
for d in dbs:
d = os.path.abspath(os.path.expanduser(d))
if d not in seen:
seen.add(d)
out.append(d)
for group in _nssdb_groups():
paths = [os.path.abspath(os.path.expanduser(p)) for p in group]
existing = [p for p in paths if os.path.exists(os.path.join(p, "cert9.db"))]
for p in existing or paths[:1]:
if p not in seen:
seen.add(p)
out.append(p)
return out


Expand Down Expand Up @@ -529,6 +574,7 @@ def _ensure_leaf_trust(name, ip):
if is_ca:
log("pit: https for %s: certificate is CA:TRUE — refusing to trust a key that could vouch for any name" % name)
return False, "ca-true"
remember_pin(name, pin)
try:
os.makedirs(PIT_CERT_DIR, mode=0o700, exist_ok=True)
cert_file = os.path.join(PIT_CERT_DIR, "moshpit-%s.crt" % name)
Expand Down
60 changes: 48 additions & 12 deletions apps/desktop/launcher/tronbrowser
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ if [ "$TOR" != "1" ]; then
# running helper isn't this version — otherwise leave a healthy current
# helper alone (don't drop an active Tor session). All backgrounded so the
# kill+settle never holds up the browser launch.
HELPER_VERSION=3.4.1
HELPER_VERSION=3.4.2
(
_pf="$DATA/tor-helper.pid"
_rv="$(curl -fsS --max-time 1 http://127.0.0.1:9061/status 2>/dev/null | sed -n 's/.*"version"[^"]*"\([^"]*\)".*/\1/p')"
Expand All @@ -187,14 +187,18 @@ if [ "$TOR" != "1" ]; then
lsof -ti tcp:9061 2>/dev/null | while read -r _p; do kill "$_p" 2>/dev/null || true; done
fi
sleep 1 # let the control port free up before re-binding
# A Flatpak engine reads ~/.var/app/<app>/.pki/nssdb (its --persist=.pki),
# so the helper's per-name trust must land there, not in ~/.pki/nssdb.
# A Flatpak engine reads one of ~/.var/app/<app>/.pki/nssdb (its
# --persist=.pki) or ~/.var/app/<app>/data/pki/nssdb (its XDG_DATA_HOME,
# Chromium's default since M146) — never the real ~/.pki. Name both; the
# helper writes whichever exist.
_pit_db_extra=""
if [ "$BROWSER" = "flatpak" ] && [ -n "$FLATPAK_APP" ]; then
_pit_db_extra="$HOME/.var/app/$FLATPAK_APP/.pki/nssdb"
_pit_db_extra="$HOME/.var/app/$FLATPAK_APP/.pki/nssdb:$HOME/.var/app/$FLATPAK_APP/data/pki/nssdb"
fi
_pit_engine=native
[ "$BROWSER" = "flatpak" ] && _pit_engine=flatpak
exec env TRON_TOR_DATA="$DATA/tor" TRON_TOR_BIN_DIR="$DIR" TRON_TOR_PIDFILE="$_pf" \
TRON_PIT_NSSDB_EXTRA="$_pit_db_extra" \
TRON_PIT_NSSDB_EXTRA="$_pit_db_extra" TRON_PIT_ENGINE="$_pit_engine" \
python3 "$DIR/tron-tor-helper"
fi
) >>"$DATA/tor-helper.log" 2>&1 &
Expand Down Expand Up @@ -564,14 +568,28 @@ sync_moshpit_trust() {
if [ "$(uname -s)" != "Linux" ]; then return 0; fi
if [ "${TRONBROWSER_NO_MOSHPIT_TRUST:-0}" = "1" ]; then return 0; fi

# A Flatpak engine is sandboxed with --persist=.pki: inside it, ~/.pki is
# ~/.var/app/<app>/.pki, so that database is the one it actually reads and
# an import into the real ~/.pki/nssdb never reaches it. Write both.
_flatdb=""
# Chromium opens ONE of two databases per home — the legacy ~/.pki/nssdb or,
# since M146, ${XDG_DATA_HOME:-~/.local/share}/pki/nssdb — and which one a
# build picks has changed between versions. A Flatpak engine is sandboxed
# with --persist=.pki and XDG_DATA_HOME=~/.var/app/<app>/data, so its two are
# ~/.var/app/<app>/.pki/nssdb and ~/.var/app/<app>/data/pki/nssdb, and the
# real ~/.pki is invisible to it. Write every candidate that already exists;
# create the legacy one only when neither does (a fresh ~/.pki/nssdb beside a
# populated XDG database would flip a newer Chromium onto an empty store).
_xdgdb="${XDG_DATA_HOME:-$HOME/.local/share}/pki/nssdb"
_dbs=""
if [ -f "$_xdgdb/cert9.db" ]; then _dbs="$_xdgdb"; fi
if [ -f "$HOME/.pki/nssdb/cert9.db" ] || [ -z "$_dbs" ]; then _dbs="$HOME/.pki/nssdb${_dbs:+ $_dbs}"; fi
if [ "$BROWSER" = "flatpak" ] && [ -n "$FLATPAK_APP" ]; then
_flatdb="$HOME/.var/app/$FLATPAK_APP/.pki/nssdb"
_fa="$HOME/.var/app/$FLATPAK_APP"
_fdbs=""
if [ -f "$_fa/data/pki/nssdb/cert9.db" ]; then _fdbs="$_fa/data/pki/nssdb"; fi
if [ -f "$_fa/.pki/nssdb/cert9.db" ] || [ -z "$_fdbs" ]; then _fdbs="$_fa/.pki/nssdb${_fdbs:+ $_fdbs}"; fi
_dbs="$_dbs $_fdbs"
fi
for _nssdb in "$HOME/.pki/nssdb" ${_flatdb:+"$_flatdb"}; do
# $_dbs is a space-separated list of paths under $HOME; a home with a space
# in it is not something the rest of this launcher survives either.
for _nssdb in $_dbs; do
_ready=0

# Each word below is a literal path or a glob result, so this stays
Expand Down Expand Up @@ -665,7 +683,25 @@ esac
# bundle one, but the kill switch is not ours to flip on their behalf.
# (Ungoogled keeps MV2, but force it off everywhere to be safe.)
MV2_KEEP="ExtensionManifestV2Disabled,ExtensionManifestV2Unsupported,ExtensionManifestV2DeprecationWarning,ExtensionManifestV2DeprecationUnsupported"
FLAGS="--user-data-dir=$DATA --class=TronBrowser --no-first-run --no-default-browser-check --no-pings --disable-background-networking --disable-breakpad --disable-domain-reliability --disable-sync --disable-features=Translate,OptimizationHints,InterestFeedContentSuggestions,$MV2_KEEP$GPU_OFF_FEATURES --load-extension=$EXT"
# Flatpak engine only: the Flathub ungoogled-chromium ignores NSS user trust (the
# pit helper's per-name import sat in the very database strace showed it opening,
# and Chromium still answered "No matching issuer found"), but it honours pins
# given on the command line. The helper records every pin it accepted — a key the
# registry vouches for, for that name — in $HOME/.tronbrowser/pit-certs/pins.txt;
# hand them over here. Chromium shows a one-line "unsupported command-line flag"
# bar at start when this switch is present, so it is only passed when there is at
# least one pin, i.e. on a machine that has used https on a pit name. Native
# engines honour NSS and get nothing here.
pit_spki_flag() { # pins_file -> prints the switch, or nothing
[ -s "$1" ] || return 0
_pins="$(awk 'NF >= 2 && $2 !~ /[^A-Za-z0-9+\/=]/ { print $2 }' "$1" | sort -u | paste -sd, -)"
[ -n "$_pins" ] && printf -- '--ignore-certificate-errors-spki-list=%s' "$_pins"
}
PIT_SPKI_FLAG=""
if [ "$BROWSER" = "flatpak" ]; then
PIT_SPKI_FLAG="$(pit_spki_flag "$HOME/.tronbrowser/pit-certs/pins.txt")"
fi
FLAGS="--user-data-dir=$DATA --class=TronBrowser --no-first-run --no-default-browser-check --no-pings --disable-background-networking --disable-breakpad --disable-domain-reliability --disable-sync --disable-features=Translate,OptimizationHints,InterestFeedContentSuggestions,$MV2_KEEP$GPU_OFF_FEATURES --load-extension=$EXT${PIT_SPKI_FLAG:+ $PIT_SPKI_FLAG}"

# Make the tab-strip audio indicator a clickable mute/unmute control. Upstream
# media::kEnableTabMuting is DISABLED_BY_DEFAULT and stock Chrome only turns it
Expand Down
37 changes: 29 additions & 8 deletions docs/moshpit-pit-toggle.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🤘 Pit toggle — Moshpit names for one browser session

**Status:** shipped with the AI-sidebar extension + `tron-tor-helper` 3.4.1
**Status:** shipped with the AI-sidebar extension + `tron-tor-helper` 3.4.2
**Owner:** desktop (`apps/desktop`)
**Scope:** resolve Moshpit names in the running browser with one click. Not a
replacement for `moshcode dns enable`, which does it for the whole machine.
Expand Down Expand Up @@ -79,13 +79,34 @@ with root. The pit toggle does the no-root equivalent for this browser:
4. All of this happens before the SOCKS reply, so the browser's TLS handshake
that follows already finds the certificate trusted.

The import goes into every database the engine might read: `~/.pki/nssdb`,
the database the launcher names for the engine it started, and any
`~/.var/app/*chromium*/.pki/nssdb`. That last part matters: the Flathub
ungoogled-chromium is sandboxed with `--persist=.pki`, so inside it `~/.pki`
is `~/.var/app/io.github.ungoogled_software.ungoogled_chromium/.pki`, and an
import into the real `~/.pki/nssdb` never reaches it (the launcher's Local CA
sync had the same blind spot and now writes both).
Chromium opens one of two databases per home: the legacy `~/.pki/nssdb`, or
since M146 `${XDG_DATA_HOME:-~/.local/share}/pki/nssdb`, and which one a build
picks has changed between versions. A Flatpak engine is sandboxed with
`--persist=.pki` and `XDG_DATA_HOME=~/.var/app/<app>/data`, so its two
candidates are `~/.var/app/<app>/.pki/nssdb` and
`~/.var/app/<app>/data/pki/nssdb`, and the real `~/.pki` is invisible to it.
The helper and the launcher's Local CA sync therefore write every candidate
that already exists for each home (the real one, the engine's, and any
`~/.var/app/*chromium*`), and create the legacy one only when none exists,
so a browser is never flipped onto a fresh empty store. Found on bonita:
Flatpak ungoogled-chromium 152 read `data/pki/nssdb` while `.pki/nssdb`
existed beside it, and Chromium's net log said "No matching issuer found"
until that database held the leaf too.

**Flatpak engines do not honour NSS user trust at all.** Found on bonita with
the Flathub ungoogled-chromium 152: `strace` showed Chromium opening the very
database that held the leaf (peer and anchor trust both tried), single-process
and no-sandbox made no difference, and its net log still said "No matching
issuer found". What that build does honour is
`--ignore-certificate-errors-spki-list`, Chromium's own per-key allowance. So
the helper also records every pin it accepts in
`~/.tronbrowser/pit-certs/pins.txt`, and for a Flatpak engine the launcher
passes those pins on the command line at start. Two consequences: a name first
trusted mid-session loads over https after one relaunch (the sidebar says so),
and Chromium shows its one-line "unsupported command-line flag" bar at start on
a machine that has such pins. Native engines take the NSS path and get neither.
The clean way out is a Moshpit CA on the registry side, or shipping the
portable ungoogled-chromium as TronBrowser's own engine, which honours NSS.

Linux only for now (Chromium on macOS reads the keychain, which needs an
interactive prompt), and it needs `certutil` (Debian/Ubuntu `libnss3-tools`,
Expand Down
Loading