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
70 changes: 57 additions & 13 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.0"
HELPER_VERSION = "3.4.1"
_lock = threading.Lock()
_proc = None # the running tor subprocess (or None)
_ready = False # True once tor reported Bootstrapped 100%
Expand Down Expand Up @@ -347,6 +347,12 @@ 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.
PIT_NSSDB_EXTRA = os.environ.get("TRON_PIT_NSSDB_EXTRA", "")
PIT_CERT_DIR = os.environ.get("TRON_PIT_CERT_DIR", os.path.expanduser("~/.tronbrowser/pit-certs"))
_trust_lock = threading.Lock()
_trust_seen = {} # name -> (ok, why); retried after a failure only once the pit restarts
Expand Down Expand Up @@ -435,14 +441,50 @@ def trust_available():
return {"available": False, "why": "unsupported-platform"}
if not shutil.which("certutil"):
return {"available": False, "why": "no-certutil"}
return {"available": True, "why": "certutil"}
return {"available": True, "why": "certutil", "nssdbs": pit_nssdbs()}


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)
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)
return out


def _certutil(*args):
return subprocess.run(["certutil", "-d", "sql:" + PIT_NSSDB] + list(args),
def _certutil(db, *args):
return subprocess.run(["certutil", "-d", "sql:" + db] + list(args),
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=20)


def _db_ready(db):
"""Create the database if it is missing, like the launcher does. False on failure."""
if os.path.exists(os.path.join(db, "cert9.db")):
return True
try:
os.makedirs(db, mode=0o700, exist_ok=True)
except OSError:
return False
return _certutil(db, "-N", "--empty-password").returncode == 0


def _import_leaf(db, nick, cert_file):
"""'already' | 'trusted' | 'failed' for one database."""
if not _db_ready(db):
return "failed"
if _certutil(db, "-L", "-n", nick).returncode == 0:
return "already"
return "trusted" if _certutil(db, "-A", "-t", "P,,", "-n", nick, "-i", cert_file).returncode == 0 else "failed"


def ensure_leaf_trust(name, ip):
"""Make the browser trust what `name` serves on 443, if the registry vouches
for it. Returns (ok, why); never raises. Runs once per name per pit run."""
Expand All @@ -463,11 +505,9 @@ def _ensure_leaf_trust(name, ip):
if not name or "." not in name:
return False, "bad-name"
nick = "moshpit %s" % name
if not os.path.exists(os.path.join(PIT_NSSDB, "cert9.db")):
os.makedirs(PIT_NSSDB, mode=0o700, exist_ok=True)
if _certutil("-N", "--empty-password").returncode != 0:
return False, "nssdb-create-failed"
if _certutil("-L", "-n", nick).returncode == 0:
dbs = pit_nssdbs()
missing = [db for db in dbs if not (_db_ready(db) and _certutil(db, "-L", "-n", nick).returncode == 0)]
if not missing:
return True, "already-trusted"
try:
der = served_certificate(ip, name)
Expand Down Expand Up @@ -496,11 +536,15 @@ def _ensure_leaf_trust(name, ip):
f.write(ssl.DER_cert_to_PEM_cert(der))
except OSError as exc:
return False, "write-failed: %s" % exc
res = _certutil("-A", "-t", "P,,", "-n", nick, "-i", cert_file)
if res.returncode != 0:
log("pit: https for %s: certutil failed: %s" % (name, res.stdout.strip()))
done, failed = [], []
for db in missing:
(done if _import_leaf(db, nick, cert_file) != "failed" else failed).append(db)
if failed:
log("pit: https for %s: certutil could not write %s" % (name, ", ".join(failed)))
if not done and failed:
return False, "certutil-failed"
log("pit: https for %s: trusted its certificate (pin %s matches the registry) in %s" % (name, pin, PIT_NSSDB))
log("pit: https for %s: trusted its certificate (pin %s matches the registry) in %s"
% (name, pin, ", ".join(done)))
return True, "trusted"


Expand Down
21 changes: 18 additions & 3 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.0
HELPER_VERSION=3.4.1
(
_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,7 +187,14 @@ 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.
_pit_db_extra=""
if [ "$BROWSER" = "flatpak" ] && [ -n "$FLATPAK_APP" ]; then
_pit_db_extra="$HOME/.var/app/$FLATPAK_APP/.pki/nssdb"
fi
exec env TRON_TOR_DATA="$DATA/tor" TRON_TOR_BIN_DIR="$DIR" TRON_TOR_PIDFILE="$_pf" \
TRON_PIT_NSSDB_EXTRA="$_pit_db_extra" \
python3 "$DIR/tron-tor-helper"
fi
) >>"$DATA/tor-helper.log" 2>&1 &
Expand Down Expand Up @@ -557,7 +564,14 @@ 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"
# 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=""
if [ "$BROWSER" = "flatpak" ] && [ -n "$FLATPAK_APP" ]; then
_flatdb="$HOME/.var/app/$FLATPAK_APP/.pki/nssdb"
fi
for _nssdb in "$HOME/.pki/nssdb" ${_flatdb:+"$_flatdb"}; do
_ready=0

# Each word below is a literal path or a glob result, so this stays
Expand Down Expand Up @@ -601,9 +615,10 @@ sync_moshpit_trust() {
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
echo "TronBrowser: trusted $_nick for Moshpit HTTPS ($_nssdb)." >&2
fi
done
done
return 0
}

Expand Down
10 changes: 9 additions & 1 deletion 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.0
**Status:** shipped with the AI-sidebar extension + `tron-tor-helper` 3.4.1
**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,6 +79,14 @@ 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).

Linux only for now (Chromium on macOS reads the keychain, which needs an
interactive prompt), and it needs `certutil` (Debian/Ubuntu `libnss3-tools`,
Fedora `nss-tools`, Arch `nss`); `install.sh` installs it on machines that have
Expand Down
Loading