Moshpit CA: the registry signs a certificate for every name it holds - #519
Merged
Merged
Conversation
No public CA issues for a name outside the ICANN root, so https on a Moshpit name meant a self-signed leaf per origin and a client learning each name against its pin. The registry already decides who holds a name, so it now signs: an offline root, an online issuing intermediate (MOSHPIT_CA_CERT/KEY/ROOT, base64 PEM), and 30-day CA:FALSE leaves with DNS:<name> and *.<name>, issued to whoever controlledName() says controls the name, the holder or the current tenant. That is what keeps resale safe: the ending owner cannot obtain a certificate for a name they sold, and never holds a signing key. Real TLDs are refused unconditionally, the CSR never decides the subject, 24 issuances a day per name, and every leaf's key is published as a tls pin so pin clients keep working.
Routes: GET /api/moshpit/ca, ca.crt, ca-chain.crt; POST /api/moshpit/tlds/:tld/certs {label, csr}; GET .../certs?label= and /api/moshpit/certs/:serial. Migration 024 records issuances. scripts/moshpit-ca-init.mjs makes the root and issuer once, offline. @peculiar/x509 (already transitively present) becomes a direct dependency. Tests verify the chain with node's X509Certificate, the resale and lease rules, the refusals, and the HTTP shapes; full suite 767/767.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| if (leaf.error) return { ok: false, error: leaf.error, status: leaf.status || 400 }; | ||
|
|
||
| await run( | ||
| `INSERT INTO moshpit_name_certs (${CERT_COLS}) VALUES (?,?,?,?,?,?,?,?,?)`, |
|
|
||
| /** One issued certificate by serial, with its PEM. */ | ||
| export async function getNameCertificate(serial) { | ||
| return get(`SELECT ${CERT_COLS} FROM moshpit_name_certs WHERE serial = ?`, [String(serial || "")]); |
ThreatCrush Security Scan42 finding(s) in the 13 file(s) this pull request changes. MEDIUM: 42
57 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 8 | **MEDIUM**: 38 | **LOW**: 11Not introduced by this pull request. The full set is in the Security tab.
…and 37 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
…--frozen-lockfile Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ralyodio
added a commit
that referenced
this pull request
Sep 16, 2026
…nable and a new dns ca verb (#520) The registry now signs a certificate for every name it holds (#519), so a machine that trusts its root once trusts every Moshpit name over https: curl, Firefox, Chromium, git. dns enable fetches /api/moshpit/ca and /api/moshpit/ca.crt, checks the served root is a self-signed CA:TRUE certificate whose fingerprint matches what the registry reports, keeps it at ~/.moshpit/ca/registry-root.crt, and installs it into the NSS store (no root) and the system store (as root) under the nickname Moshpit Root CA, the same nickname TronBrowser's launcher uses so neither imports the other's work twice. dns disable takes it out. moshcode dns ca does only that half, for a machine whose resolution comes from elsewhere; --remove undoes it. trustStores/untrustPlan take the nickname and system file as parameters, defaults unchanged. The root carries no name constraints, since X.509 cannot say everything except the ICANN root across seventeen thousand endings; the signer is the bound, and installing it stays an explicit act. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 16, 2026
ralyodio
added a commit
to profullstack/tronbrowser.dev
that referenced
this pull request
Sep 16, 2026
…the launcher on every start (#109) 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 <noreply@anthropic.com>
ralyodio
added a commit
to profullstack/moshpit-proxy
that referenced
this pull request
Sep 16, 2026
…imer (#25) The registry now runs a certificate authority for the names it holds (moshcoder/moshcode#519). With MOSHPIT_API_KEY set, setup-origin.sh sends a CSR for the name to POST /api/moshpit/tlds/:tld/certs and writes the chain it returns (leaf, issuer, root) over the certificate nginx serves, so any client that trusts the pit's root, TronBrowser or a box that ran moshcode dns enable, accepts the name with no pin lookup and no per-name import. Same key, so the published pin does not move and pin-checking clients keep working. The self-signed certificate stays as the fallback: no key, --self-signed, a registry without a CA, or a refusal all leave it in place and say so. With a signed chain the box trusts the pit's root instead of the leaf, under the same file moshcode uses. The 30-day leaf is renewed by scripts/moshpit-renew.sh, run daily by moshpit-renew.timer, which the script installs the first time it gets a signed certificate; the API key it needs is kept root-only at /etc/moshpit/renew.env. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The registry becomes a certificate authority for the names it holds. A client that installs the Moshpit root once trusts every pit name over https, instead of learning each name against its pin. TronBrowser will bundle the root and
moshcode dns enablewill install it (separate PRs); origins get 30-day leaves from the registry (setup-origin.sh change in moshpit-proxy, separate PR).Why the registry, and why per name
No public CA issues for a name outside the ICANN root. The registry is the one party that already knows who holds a name, so it is the natural signer. Issuance uses
controlledName(), the same rule pins use: the name's holder, or its tenant during a lease. That is what makes resale safe: whoever holds.foocannot obtain a certificate forbar.fooonce it is somebody else's, and ending owners never hold a signing key. Only the registry signs.Design
Moshpit Root CA, 20 years, pathlen 1) signs an online issuing intermediate (Moshpit Issuing CA, 10 years, pathlen 0). The service holds only the issuer:MOSHPIT_CA_CERT,MOSHPIT_CA_KEY,MOSHPIT_CA_ROOT, PEM or base64 of PEM. On load it checks the key matches the certificate and the issuer chains to the root, and refuses a half-configured CA. Unconfigured =enabled: false/ 503, nothing else changes.CN=<name>,DNS:<name>+DNS:*.<name>,CA:FALSE,serverAuth, 30 days (MOSHPIT_CA_LEAF_DAYS), ECDSA P-256 signatures. Short lives instead of revocation.isRealTld), so a registry bug cannot mintgoogle.com. CSR signature must verify; RSA < 2048 refused. 24 issuances per name per day.tlspin (notemoshpit-ca), so pin clients keep working through the switch.@peculiar/x509was already in the tree transitively via@simplewebauthn/server; it becomes a direct dependency (11-line lock diff).Routes
Migration
024_moshpit_certs.sqlrecords issuances (serial, pin, PEM, who, when).scripts/moshpit-ca-init.mjsmakes the root and issuer once, offline, and writes the three service variables as base64 one-liners.apps/pwa/docs/moshpit-ca.mddocuments all of it.Verified
test/moshpit-ca.test.mjs(16): the generated CA is a real chain under Node'sX509Certificate; a leaf verifies against the issuer, names the host and*.hostand nothing else, is not a CA, lasts 30 days, and its pin is published; the CSR's own subject is ignored; the ending owner is refused for a sold name and the buyer is not; during a lease the tenant may and the holder may not; real TLD refused; bad and tampered CSRs refused; unregistered name refused; issuance recorded; renewal loop stopped at 24; the root/chain material is right.test/moshpit-ca-route.test.mjs(2): 503 andenabled: falsewith no CA; once configured, PEM root and chain with the right content type, a bearer key mints a leaf for its own name, another user's key gets 403, no key gets 401, the issued list and per-serial fetch work,/api/moshpit/pinsreturns the leaf's pin.Production
The real root and issuer were generated with the init script. The root key and all material are in the logicsrc vault
profullstack / moshpit-ca / prod. The three service variables are already set on themoshcodeRailway service (--skip-deploys), so the CA is live the moment this merges and redeploys:GET https://pit.moshcode.sh/api/moshpit/cawill reportenabled: true.🤖 Generated with Claude Code