| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Registry-pinned TLS for Moshpit names. The origin's key is checked against what the registry published for that name — not against a certificate authority, because no certificate authority will ever vouch for .moshpit.
You can't, and it isn't a matter of persuasion. The CA/Browser Forum Baseline Requirements banned certificates for non-IANA names: issuance stopped in November 2015 and existing ones were revoked by October 2016. A CA in a browser root store that issued for .moshpit would be distrusted for doing it. The rule is the penalty.
So the trust has to come from somewhere else. It comes from the registry, which already knows who owns each name — a stronger claim than domain validation ever made, since it proves registry ownership rather than momentary control of a socket.
| Gateway sees | ||
|---|---|---|
| A. Gateway terminates TLS | everything | simplest, but the gateway is a designed-in MITM |
| B. Gateway SNI-passthrough | hostname only | implemented here |
| C. Registry returns origin IP | nothing | leaks origin IPs, breaks NAT'd sites |
A central terminating gateway and end-to-end encryption are mutually exclusive. This is topology B: nginx routes on the SNI without a private key, so the session that terminates at the origin is genuinely end-to-end.
tronbrowser.dev has built-in Moshpit support. It resolves names and verifies registry pins natively, so it needs no DNS configuration, no local proxy, and no root certificate. Nothing in this package is required to browse Moshpit from TronBrowser.
Everything below exists for stock browsers, which cannot be taught to check a registry pin — there is no certificate-validation API in any browser extension model.
browser --TLS--> proxy --TLS--> gateway (ssl_preread) --TCP--> origin
local CA registry-pinned key
The right-hand session is the real one. The left-hand session exists only to say the result in the one language a browser accepts.
npm start # or: node bin/moshpit-proxy.tsPair it with moshpit-dns, which points .moshpit names at loopback.
| Variable | Default | |
|---|---|---|
| MOSHPIT_PROXY_PORT | 8443 | 443 needs privileges; the installer moves it |
| MOSHPIT_PROXY_HOST | 127.0.0.1 | |
| MOSHPIT_GATEWAY_HOST | pit.moshcode.sh | |
| MOSHPIT_REGISTRY_BASE | https://pit.moshcode.sh | |
| MOSHPIT_PROXY_TLDS | moshpit | comma-separated |
| MOSHPIT_PROXY_PINS | ~/.moshpit/pins.json | overrides, win over the registry |
| MOSHPIT_PROXY_TOFU | off | see below |
| MOSHPIT_PROXY_DIR | ~/.moshpit |
The proxy generates a root on first run at ~/.moshpit/ca/ca.crt and prints its fingerprint. Two things make installing it a much smaller ask than a shared CA:
That second property is tested, not asserted — tests/ca.test.ts signs a www.google.com certificate with the root and checks that openssl rejects it with permitted subtree violation.
# Linux (NSS: Chrome, Firefox)
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "Moshpit Local CA" -i ~/.moshpit/ca/ca.crt
# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ~/.moshpit/ca/ca.crtNode, Python and Java keep their own trust stores — NODE_EXTRA_CA_CERTS, certifi, cacerts respectively. Installing into the OS store does not cover them.
On the box that will host the name — a VPS, a droplet, anything with a public address:
sudo sh scripts/setup-origin.sh chovy.hackerGenerates a key and self-signed certificate, writes an nginx server block from nginx/moshpit-origin.conf, reloads, connects back to itself to prove the name now answers with the key it just made, and prints the pin to publish. --dry-run shows what it would do.
Self-signed is the design, not a shortcut: no CA will issue for a Moshpit TLD, so identity comes from the registry rather than an issuer. Nothing about the certificate is checked except the key, so an expensive one and this one are worth exactly the same here.
The failure this is built to prevent: a name with no matching server block falls through to nginx's default vhost, which answers with a certificate for some other name entirely. The browser reports an invalid certificate and the site looks broken, when what is broken is one missing server_name. The script connects back and checks, rather than trusting that a clean nginx -t means the name resolves to the right block.
Nothing is proxied on the server side. moshpit-proxy runs on the visitor's machine — it is the thing that checks the pin on behalf of a browser that cannot.
npx moshpit-pin ./origin.crt # from a file
npx moshpit-pin scrambled.eggs:443 # from what a live server presentssetup-origin.sh prints this for you. Until the pin is published every client refuses the name — there is no TOFU and no unauthenticated mode, because the pin stands exactly where a certificate authority would.
Publish the result at app.moshcode.sh/pit/dns. Keep the previous pin listed alongside the new one while rotating — the client accepts any pin in the list, so a key can change without a flag day.
The pin is SHA-256(SubjectPublicKeyInfo), base64 — the RFC 7469 format, so openssl x509 -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 prints the same string. tests/spki.test.ts asserts we agree with openssl.
nginx/moshpit-gateway.conf — there is no ssl_certificate directive anywhere in it, which is the point. Requires ngx_stream_module with ssl_preread (already compiled into stock Debian/Ubuntu nginx).
node scripts/gen-upstreams.ts # writes the SNI->origin map, reloads nginxThe map is regenerated on a timer rather than looked up per connection, so the registry is not on the path of every TCP handshake.
Because both ends of the trust decision are ours, server keys can move to ML-DSA whenever we like — no CA, no root program, no CA/B ballot. Browsers don't support PQ signatures in TLS yet, but the proxy terminates for the browser, so the proxy↔origin leg can run ML-DSA before the public web can.
The proxy configures no groups at all — the origin leg uses Node's defaults. On Node 24 against OpenSSL 3.5 those defaults already put the hybrid first and send a real ML-KEM key share in the first flight. Read off the wire from the ClientHello the proxy actually sends:
supported_groups: X25519MLKEM768, x25519, secp256r1, x448, secp384r1, ... key_share sent : X25519MLKEM768(1216B), x25519(32B)
An origin on OpenSSL 3.0–3.4 — what Ubuntu 22.04 and 24.04 still ship — has no ML-KEM, so the handshake silently falls back to x25519 and succeeds. The session is fine against every adversary that exists today and decryptable by one that doesn't yet. Nothing said so, which made the guarantee unobservable.
Every upstream leg is now classified and counted:
[proxy] ok scrambled.eggs (registry) TLSv1.3 hybrid-pq [proxy] warn old.eggs: origin has no ML-KEM, fell back to classical/X25519 ... [proxy] 41 post-quantum, 3 classical
MOSHPIT_PROXY_REQUIRE_PQ=1 turns the fallback into a refusal. Leave it off until the counters say the grid is ready — switching it on today takes every pre-3.5 origin offline.
Node exposes no binding for SSL_get_negotiated_group(), so the group is read through getEphemeralKeyInfo(), which cannot represent a hybrid KEM and returns {} for one while naming any classical group. That is an inference from an absence, so it is never trusted on faith: two loopback handshakes at startup prove both halves of the mapping, the result is printed, and enforcement refuses to engage if the proof fails. See lib/pq.ts.
The rest of the Moshpit stack is Bun. This is not, and the reason is specific rather than aesthetic. Bun 1.3.14 cannot do dynamic SNI:
A proxy that mints a certificate per name needs all of the above. Node 24 supports every part of it and runs the TypeScript directly via type stripping, so there is no build step. If Bun's node:tls catches up, this moves back.
npm test # node --test tests/*.test.ts27 tests. The ones that matter are in tests/proxy.test.ts: they stand up a real origin over real TLS and check that a vouched-for key gets through and a substituted one is refused before any request byte leaves the machine.
Apache-2.0. See LICENSE and NOTICE.
Apache rather than MIT for a specific reason: this is protocol code meant to be reimplemented — TronBrowser performs the same pin verification independently — and Apache-2.0 carries an explicit patent grant where MIT is silent. Implement the pin scheme however you like; the NOTICE covers naming.
Security policy and the trust model behind the local CA: SECURITY.md.
| Back | FazBrowse Home | New Git URL |