HNS.ONE

Docs · DANE

TLS with no certificate authority in it.

No CA can issue a certificate for a Handshake name — there is no registrar to validate it against. DANE replaces that whole layer: the fingerprint of the certificate is published in your name's own signed zone, anchored on-chain by a record only you can commit. A client that checks it trusts nobody but you.

The pin

One record shape, one profile, and nothing else is emitted or accepted:

_443._tcp.<label>   TLSA   3 1 1 <sha256 of the cert's SubjectPublicKeyInfo>
_443._tcp           TLSA   3 1 1 <...>      # at the zone apex

3 1 1 is DANE-EE / SPKI / SHA-256: the certificate is trusted because it is this key, not because someone vouched for it. Because the pin is over the public key rather than the whole certificate, renewing the certificate without rotating the key leaves the pin valid.

The certificate on a Handshake name is self-signed — EC P-256, CN=<name>, with the name and its wildcard as SANs. There is nothing for a CA to do and no CA is asked.

# what the pin should be, from a certificate
openssl x509 -in cert.pem -noout -pubkey \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256

Who signs the DS

DANE only means anything if the TLSA record itself is provable, and that needs a DNSSEC chain that terminates in something nobody but the name's owner can write. On Handshake that terminus is the on-chain DS record.

PieceWhoNotes
Certificate and private keyHNS.ONE Generated once and reused; the key is never rotated on renewal
TLSA record in the zoneHNS.ONE Written only after the browser gate passes
Zone signing key and RRSIGsHNS.ONE One combined key per zone, algorithm 13, flags 257. It never leaves the operator's machine — the edge terminates TLS but cannot sign a zone
The DS record on-chainYou Digest type 2. We hand you the four fields; only your key can commit them

The split is on wallet ownership, and it fails safe: anything other than a clean, positive "this wallet holds the name" is treated as external and produces a handoff rather than a signature. We do not assume we may anchor a name we cannot demonstrably prove we hold.

Enable it and read its state:

POST https://app.hns.one/api/dane/enable   {"name": "yourname", "label": "www"}
GET  https://app.hns.one/api/dane/status?name=yourname

For a name you hold, the response carries the exact commit_on_chain_ds object to publish, plus the TLSA we serve. Nothing is signed or published if the gate below rejects it.

The browser gate

Before a pin is signed and published, the exact validator that ships in Wildroot is run against it, imported as a module rather than reimplemented. If it rejects the chain, nothing is written — no partial state, no "publish and hope".

The reasoning is that a DANE pin is a promise about how a client will behave. Publishing one the client would refuse is worse than publishing none: it takes the name down for exactly the clients that check.

What a client actually does

  • Wildroot, on a Handshake name — validates, and fails closed The socket is opened with WebPKI verification off, because the trust is DANE, not a CA. On mismatch the socket is destroyed and the page becomes HTTP 526 Certificate not trusted. There is no CA fallback anywhere in that path. Connections carrying a DANE verdict are never pooled, so a keep-alive reuse cannot skip the check.
  • No pin published — plain HTTP, and only on a proved absence If the zone gives an authoritative "no such record", the client proceeds unencrypted and marks it untrusted. A SERVFAIL or a refused query is not an absence: those fail closed with HTTP 525 No secure route.
  • Unsupported TLSA profile — treated as a mismatchA zone that publishes a profile the client cannot check has still stated a policy. Ignoring it would let that policy be silently discarded.
  • Every other browser — no DANE at allChrome, Firefox and Safari do not implement it and will not. That is why the <name>.hns.one gateway exists with an ordinary CA certificate, and why the two paths are two different TLS identities for the same page.
Two paths in Wildroot that carry no DANE guarantee

DoH fallback. When the client resolves over DoH instead of its own node — which includes whenever the private-lookup mode is on — it cannot prove a pin, so the address is served over plain HTTP and marked untrusted rather than being silently upgraded.

The Android build. Its DANE support is accept-on-match only: a CA-valid certificate that mismatches the pin is currently accepted, and an HTTPS failure downgrades rather than refusing. Full fail-closed behaviour is not shipped there.

Rotation

The primary defence is not rotating. Certificate renewal reuses the existing private key, so the certificate changes and the pin does not. That has already been exercised: our gateway certificate was reissued with a different SAN set and an unchanged public key, and every pin stayed valid.

Where a key genuinely has to change, the correct procedure is additive — publish the new pin alongside the old, let it propagate, then serve the new certificate, then remove the old pin. A client mid-propagation always matches one of the two.

A certificate-renewal hook checks, on every renewal, that the published pins still match the certificate about to be installed, over DNSSEC-validated lookups. A lookup it cannot complete is reported as "could not be verified", never as "the key moved" — the distinction matters, because acting on the wrong one publishes a wrong pin.

Handshake zones do not get the two-pin overlap yet

Enabling DANE on a name through the panel replaces the TLSA at that owner name rather than adding beside it. Today that is masked by key reuse — the pin does not move — but the code has no dual-pin path, so a genuine key change on a Handshake name is a hard cutover with a propagation window where checking clients fail.

What is pinned right now

NameState
14898 DS on-chain, TLSA at the apex and three labels, matching the served self-signed certificate
10103 DS on-chain, TLSA on two labels
w3, 0xme DS on-chain, DNSSEC only — no TLSA published
hns.one, mail, odoh, query Pinned on the ICANN side and validating
pds.hns.one Not pinned. It moved to its own host with its own certificate and the pin has not been re-issued
Where this is still rough

Our signer has never been checked by an independent validator. Signatures are verified by the same code that produces them, which catches implementation bugs but not a shared misreading of the spec. Treat the DNSSEC half as unvalidated until an outside validator has passed it.

Enabling DANE from the public plane is not proven. The endpoints exist and the branch that hands off to a self-custodying owner works, but the signing step there reads the operator machine's zone file rather than the tenant one and will refuse rather than sign. Ask us and it gets done on the operator side.

A name delegated elsewhere with a foreign DS cannot be fixed by us. If your name carries a DS anchoring a key we do not hold, every validating resolver fails closed on it and no amount of serving on our side helps. Remove the stale DS on-chain first.