Skip to content

One key per name, not one per ending - #253

Merged
ralyodio merged 1 commit into
mainfrom
per-name-keys
Aug 3, 2026
Merged

One key per name, not one per ending#253
ralyodio merged 1 commit into
mainfrom
per-name-keys

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

keyPaths keyed certificates off the TLD, justified in its own comment as "that is the granularity the registry stores".

That is the opposite of what the registry stores. Migration 009_moshpit_pins.sql says so, and says why:

Per name rather than per TLD, and that is forced by 008: names under a TLD are sold, so blue.eggs can belong to someone who does not own .eggs. Hanging keys off the TLD would let its operator publish a key for a name they already sold — impersonating a buyer inside the namespace they bought into.

Why this matters

A shared per-ending key is that hole in private-key form:

  • The ending's operator holds the private key for every name they have sold.
  • Every buyer holds a key that signs for every other buyer.

009 closed this at the pin layer. Keying certificates off the TLD reintroduced it one layer down, where it is worse — a pin can be withdrawn, a distributed private key cannot.

The certificate carried DNS:*.<tld> for the same reason, so each buyer's certificate asserted authority over every other name in a namespace they merely bought into. Now DNS:<name> alone.

How it surfaced

It broke nginx on a live box. moshcode site wrote conf.d blocks pointing at /etc/ssl/moshpit/hacker.crt, while the certificates on disk are per-name:

MISSING /etc/ssl/moshpit/hacker.crt
MISSING /etc/ssl/moshpit/2600.crt
MISSING /etc/ssl/moshpit/rank.crt

nginx: configuration file /etc/nginx/nginx.conf test failed

Nothing went down — nginx kept its last good config — but it could not reload, so a reboot would have taken every site on the box with it. Per-name paths agree with what setup-origin.sh has been writing all along.

Sanitising

Keeping the label means keeping the dot, so the traversal guard had to change rather than just widen:

  • chovy.hackerchovy.hacker.crt
  • alt.2600alt.2600.crt (the old alphanumerics-only strip would have eaten the dot and merged the digits into the label)
  • ../../etc/passwdetcpasswd.key — runs of dots collapse to one, so no .. survives to mean "parent"; the result is a harmless filename inside the key directory rather than a path outside it

Tests

Rewrote the two tests that pinned the old behaviour — including one whose title asserted the wrong invariant — and added coverage for the property that actually matters:

test("two names under one ending do not share a key", ...)
assert.doesNotMatch(san, /\*/, "no name may vouch for its neighbours")

841 pass, 0 fail.

Note for deployment

Boxes that already ran the old moshcode site have conf.d blocks referencing per-ending paths. On dev.profullstack.com I moved the three offending files to /root/moshpit-confd-backup/ to restore nginx -t; sites-enabled already served all three names correctly. Anywhere else will need the same, or a re-run after this ships.

🤖 Generated with Claude Code

`keyPaths` keyed certificates off the TLD, justified as "that is the
granularity the registry stores". It is the opposite of what the registry
stores. Migration 009 says so, and says why:

  Per name rather than per TLD, and that is forced by 008: names under a
  TLD are sold, so `blue.eggs` can belong to someone who does not own
  `.eggs`. Hanging keys off the TLD would let its operator publish a key
  for a name they already sold.

A shared per-ending key is that hole in private-key form. The ending's
operator holds the key for every name they sold, and every buyer holds a
key that signs for every other buyer. 009 closed it at the pin layer;
this reintroduced it one layer down, where it is worse — a pin can be
withdrawn, a distributed private key cannot.

The certificate carried `DNS:*.<tld>` for the same reason, so each
buyer's certificate asserted authority over every other name in a
namespace they merely bought into. Now `DNS:<name>` alone.

Found because it broke nginx on a live box: `moshcode site` wrote
conf.d blocks pointing at /etc/ssl/moshpit/hacker.crt while the
certificates on disk were per-name, so `nginx -t` failed with three
missing files and the box could not reload. Per-name paths agree with
what setup-origin.sh has written all along.

Path sanitising now keeps dots, so `alt.2600` stays `alt.2600.crt`.
Runs of dots collapse to one, so no `..` survives to mean "parent":
`../../etc/passwd` becomes `etcpasswd`, a harmless filename inside the
key directory.

841 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio marked this pull request as ready for review August 3, 2026 10:20
@ralyodio
ralyodio merged commit e84d695 into main Aug 3, 2026
3 checks passed
@ralyodio
ralyodio deleted the per-name-keys branch August 3, 2026 10:28
@ralyodio ralyodio mentioned this pull request Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant