diff --git a/public/.well-known/agent-skills/index.json b/public/.well-known/agent-skills/index.json index fcd113e9..4f1ce201 100644 --- a/public/.well-known/agent-skills/index.json +++ b/public/.well-known/agent-skills/index.json @@ -6,7 +6,7 @@ "type": "skill-md", "description": "Query and apply The Website Specification — a platform-agnostic specification of what a good website does. Use when the user asks what their site should have, whether something is required, how to audit a URL, what's missing for agent readiness, or anything else where you'd otherwise be guessing at web best practice. Backs answers with primary sources and ships an MCP server with search, list, fetch, checklist, and audit tools.", "url": "/.well-known/agent-skills/specification-website/SKILL.md", - "digest": "sha256:dafb93041d83177a5c40ef4d1171beca174773298111045f84992a32c90bb1d9" + "digest": "sha256:7070db105de9efc80aecd3cf5ccf3c232da7116dd182f022ea20e38631127dcd" } ] } diff --git a/public/.well-known/agent-skills/specification-website/SKILL.md b/public/.well-known/agent-skills/specification-website/SKILL.md index 1722f284..796bae40 100644 --- a/public/.well-known/agent-skills/specification-website/SKILL.md +++ b/public/.well-known/agent-skills/specification-website/SKILL.md @@ -5,7 +5,7 @@ description: Query and apply The Website Specification — a platform-agnostic s # specification.website -The Website Specification is a single source of truth for what a good website does. Ten categories, 173 pages, every item tagged with a status. It ships in three machine-readable forms: per-page Markdown, llms.txt / llms-full.txt, and an MCP server. +The Website Specification is a single source of truth for what a good website does. Ten categories, 177 pages, every item tagged with a status. It ships in three machine-readable forms: per-page Markdown, llms.txt / llms-full.txt, and an MCP server. ## When to use this skill diff --git a/public/og-default.png b/public/og-default.png index d48c2c28..bffd430f 100644 Binary files a/public/og-default.png and b/public/og-default.png differ diff --git a/public/og/checklist.png b/public/og/checklist.png index 9861b598..3bbb8e3f 100644 Binary files a/public/og/checklist.png and b/public/og/checklist.png differ diff --git a/public/og/spec.png b/public/og/spec.png index e2ee0665..a0ef486c 100644 Binary files a/public/og/spec.png and b/public/og/spec.png differ diff --git a/public/og/spec/resilience.png b/public/og/spec/resilience.png index 08f0adf9..6d93841d 100644 Binary files a/public/og/spec/resilience.png and b/public/og/spec/resilience.png differ diff --git a/public/og/spec/resilience/ipv6.png b/public/og/spec/resilience/ipv6.png new file mode 100644 index 00000000..d34e0913 Binary files /dev/null and b/public/og/spec/resilience/ipv6.png differ diff --git a/public/og/spec/security.png b/public/og/spec/security.png index f5c46914..0ac3ba34 100644 Binary files a/public/og/spec/security.png and b/public/og/spec/security.png differ diff --git a/public/og/spec/security/dane.png b/public/og/spec/security/dane.png new file mode 100644 index 00000000..e5f3c8dd Binary files /dev/null and b/public/og/spec/security/dane.png differ diff --git a/public/og/spec/security/rpki.png b/public/og/spec/security/rpki.png new file mode 100644 index 00000000..9bb19a75 Binary files /dev/null and b/public/og/spec/security/rpki.png differ diff --git a/public/og/spec/security/tls-configuration.png b/public/og/spec/security/tls-configuration.png new file mode 100644 index 00000000..51ef1cf1 Binary files /dev/null and b/public/og/spec/security/tls-configuration.png differ diff --git a/src/content/changelog/2026-10-15-ipv6-rpki-dane-tls-configuration.md b/src/content/changelog/2026-10-15-ipv6-rpki-dane-tls-configuration.md new file mode 100644 index 00000000..130d3a4e --- /dev/null +++ b/src/content/changelog/2026-10-15-ipv6-rpki-dane-tls-configuration.md @@ -0,0 +1,8 @@ +--- +title: "Added pages on IPv6, RPKI, DANE and TLS configuration" +date: "2026-10-15" +type: added +relatedSlugs: [ipv6, rpki, dane, tls-configuration] +--- + +Four pages cover the infrastructure underneath a website: [IPv6 reachability](/spec/resilience/ipv6/) for web and name servers, [RPKI route origin validation](/spec/security/rpki/), [DANE for HTTPS](/spec/security/dane/) (optional, since no mainstream browser checks it), and [TLS configuration hardening](/spec/security/tls-configuration/), which extends the HTTPS checklist with the individual parameters scanners test. diff --git a/src/content/spec/performance/compression.md b/src/content/spec/performance/compression.md index 8f16bcdc..b3e0aa96 100644 --- a/src/content/spec/performance/compression.md +++ b/src/content/spec/performance/compression.md @@ -6,8 +6,8 @@ summary: "Compress text responses with brotli where supported, gzip everywhere e status: required order: 60 appliesTo: [all] -relatedSlugs: [cache-control, vary, http3, conditional-requests, compression-dictionary-transport] -updated: "2026-07-09T00:00:00.000Z" +relatedSlugs: [cache-control, vary, http3, conditional-requests, compression-dictionary-transport, tls-configuration] +updated: "2026-10-15T00:00:00.000Z" sources: - title: "RFC 7932 — Brotli Compressed Data Format" url: "https://www.rfc-editor.org/rfc/rfc7932" @@ -70,6 +70,7 @@ Text resources — HTML, CSS, JavaScript, JSON, SVG, XML — compress to 20–30 - Compressing images or video. Pure waste. - Missing `Vary: Accept-Encoding` behind a CDN. Cache poisoning waiting to happen. - Brotli at quality 11 on dynamic responses. The encode cost erases the wire saving. +- Compressing a response that reflects attacker-controlled input next to a secret. This is the BREACH attack: an attacker who can inject text into a page (through a query parameter, say) and watch the size of the encrypted response can recover a secret on the same page, such as a CSRF token, one character at a time, because a guess that matches the secret compresses better. The fix is narrow, not a reason to switch compression off: mask per-request secrets with a fresh random value on every response (most frameworks' CSRF protection already does), keep secrets out of responses that echo user input, or skip compression on those specific responses. Static assets and pages without secrets are not affected. Scanners that flag "HTTP compression enabled" as a risk on every site, as Internet.nl does, are measuring something much broader than the actual exposure. ## Verification diff --git a/src/content/spec/resilience/ipv6.md b/src/content/spec/resilience/ipv6.md new file mode 100644 index 00000000..954ff881 --- /dev/null +++ b/src/content/spec/resilience/ipv6.md @@ -0,0 +1,75 @@ +--- +title: "IPv6 reachability" +slug: ipv6 +category: resilience +summary: "Publish AAAA records for the web server and the authoritative name servers, make sure every IPv6 address actually answers, and serve the same site over IPv6 as over IPv4." +status: recommended +order: 70 +appliesTo: [all] +relatedSlugs: [monitoring-uptime, https-tls, dnssec, rpki] +updated: "2026-10-15T00:00:00.000Z" +sources: + - title: "RFC 3596 — DNS Extensions to Support IP Version 6" + url: "https://www.rfc-editor.org/rfc/rfc3596" + publisher: "IETF" + - title: "RFC 4213 — Basic Transition Mechanisms for IPv6 Hosts and Routers" + url: "https://www.rfc-editor.org/rfc/rfc4213" + publisher: "IETF" + - title: "RFC 8305 — Happy Eyeballs Version 2: Better Connectivity Using Concurrency" + url: "https://www.rfc-editor.org/rfc/rfc8305" + publisher: "IETF" + - title: "Internet.nl — Website test" + url: "https://internet.nl/" + publisher: "Internet.nl" +--- + +## What it is + +IPv6 is the successor to IPv4, with 128-bit addresses instead of 32-bit ones. A host name is reachable over IPv6 when DNS publishes an `AAAA` record for it (RFC 3596) and something at that address answers. Running both protocols side by side, with an `A` and an `AAAA` record for the same name, is called dual stack (RFC 4213), and it is how almost every IPv6-capable site is run today. + +Two separate things need IPv6, and the second is often forgotten: + +1. **The web server.** `example.com` and `www.example.com` each need an `AAAA` record that points at a host serving the site. +2. **The authoritative name servers.** The servers listed in your `NS` records need `AAAA` records of their own, so an IPv6-only resolver can look your domain up at all. + +The wrong belief most operators hold is that a missing or broken `AAAA` record would show up as a visible outage. It usually does not. Browsers use Happy Eyeballs (RFC 8305): they try IPv6 and IPv4 almost at once and keep whichever connects first. If the IPv6 address is dead, the browser quietly falls back to IPv4 after a short delay, and the operator testing from a desktop never notices. The failure lands on clients that do not race the two protocols, on IPv6-only networks, and on tests such as Internet.nl that check each protocol on its own. + +## Why it matters + +- **IPv6-only and IPv6-preferred networks are common.** Many mobile carriers hand out only IPv6 addresses and reach IPv4 through a translation layer (NAT64). Native IPv6 skips that layer, and its latency and failure modes. +- **IPv4 addresses are scarce.** Hosts increasingly share IPv4 addresses behind carrier-grade NAT, which makes IPv4 paths slower and harder to debug than direct IPv6 ones. +- **A broken `AAAA` costs time even when it "works".** Every Happy Eyeballs fallback adds a connection delay, typically a few hundred milliseconds, before the first byte. +- **Name servers are a single point of failure.** If every authoritative server is IPv4-only, an IPv6-only resolver cannot resolve any name in your zone, however well your web server is configured. + +## How to implement + +1. **Check whether your host supports IPv6.** Most CDNs and managed platforms do, often by default. If yours does not, that is a reason to move, not a reason to skip the record. +2. **Publish `AAAA` records** for every name that has an `A` record: the apex, `www`, and any other host name that serves the site. + + ``` + example.com. 300 IN A 192.0.2.10 + example.com. 300 IN AAAA 2001:db8::10 + www.example.com. 300 IN CNAME example.com. + ``` + +3. **Make sure the server listens on IPv6.** The web server, the firewall, and any load balancer all need to accept connections on ports 80 and 443 over IPv6, with the same certificate. +4. **Serve the same site on both.** The same content, the same redirects (HTTP to HTTPS, apex to `www` or the reverse), the same security headers. Internet.nl compares the two responses and flags differences. +5. **Use name servers with IPv6.** Managed DNS providers almost all publish `AAAA` records for their name servers; check that yours do, and that at least two of them are reachable over IPv6. +6. **Monitor both protocols separately.** An uptime check that resolves the name normally will happily use IPv4 and miss a dead IPv6 path. Add a check pinned to IPv6. See [monitoring and uptime](/spec/resilience/monitoring-uptime/). + +This site ships it: the apex and both of its Cloudflare name servers have `AAAA` records, and the site answers identically over IPv6. + +## Common mistakes + +- **An `AAAA` record pointing at a host that does not listen on IPv6.** Often left over from a migration, or added by a control panel for a server whose IPv6 firewall drops port 443. Browsers hide it; other clients time out. +- **Different behaviour over IPv6.** An old virtual-host config that serves a default page, skips the HTTPS redirect, or omits HSTS on the IPv6 listener. +- **A certificate that only covers the IPv4 path.** Separate front ends per protocol drift apart, and one ends up with an expired or mismatched certificate. +- **IPv4-only name servers.** The web server is dual stack, but nothing in the delegation can be reached over IPv6. +- **Adding `AAAA` for the apex but not for `www`**, or the reverse, so half the site is reachable. + +## Verification + +- `dig +short AAAA example.com` and `dig +short AAAA www.example.com` return addresses. +- For each name server in `dig +short NS example.com`, `dig +short AAAA ` returns an address. +- `curl -6 -sI https://example.com/` and `curl -4 -sI https://example.com/` return the same status, redirects, and headers. +- Run the [Internet.nl website test](https://internet.nl/). Its IPv6 section covers name servers, web server addresses and reachability, and whether the IPv6 and IPv4 sites are the same. diff --git a/src/content/spec/resilience/monitoring-uptime.md b/src/content/spec/resilience/monitoring-uptime.md index dd772c6d..d410e048 100644 --- a/src/content/spec/resilience/monitoring-uptime.md +++ b/src/content/spec/resilience/monitoring-uptime.md @@ -6,7 +6,7 @@ summary: "Monitor the site from outside your own infrastructure, combine synthet status: recommended order: 50 appliesTo: [all] -relatedSlugs: [error-pages, maintenance-pages, third-party-scripts] +relatedSlugs: [error-pages, maintenance-pages, third-party-scripts, ipv6] updated: "2026-10-15T12:00:00.000Z" sources: - title: "Google SRE Book — Monitoring Distributed Systems" diff --git a/src/content/spec/security/caa-records.md b/src/content/spec/security/caa-records.md index 2afc6cb6..cecee0cf 100644 --- a/src/content/spec/security/caa-records.md +++ b/src/content/spec/security/caa-records.md @@ -6,7 +6,7 @@ summary: "A CAA record tells certificate authorities which of them are allowed t status: recommended order: 110 appliesTo: [all] -relatedSlugs: [https-tls, hsts, dnssec, for-sale-dns] +relatedSlugs: [https-tls, hsts, dnssec, for-sale-dns, rpki, dane] updated: "2026-05-29T09:13:20.000Z" sources: - title: "RFC 8659 — DNS Certification Authority Authorization (CAA) Resource Record" diff --git a/src/content/spec/security/dane.md b/src/content/spec/security/dane.md new file mode 100644 index 00000000..2df2c087 --- /dev/null +++ b/src/content/spec/security/dane.md @@ -0,0 +1,63 @@ +--- +title: "DANE for HTTPS" +slug: dane +category: security +summary: "A TLSA record at _443._tcp pins your HTTPS certificate or key in DNSSEC-signed DNS. No mainstream browser checks it, so for websites it is optional; its real home is SMTP." +status: optional +order: 125 +appliesTo: [all] +relatedSlugs: [dnssec, https-tls, caa-records, tls-configuration] +updated: "2026-10-15T00:00:00.000Z" +sources: + - title: "RFC 6698 — The DNS-Based Authentication of Named Entities (DANE) Transport Layer Security (TLS) Protocol: TLSA" + url: "https://www.rfc-editor.org/rfc/rfc6698" + publisher: "IETF" + - title: "RFC 7671 — The DNS-Based Authentication of Named Entities (DANE) Protocol: Updates and Operational Guidance" + url: "https://www.rfc-editor.org/rfc/rfc7671" + publisher: "IETF" + - title: "RFC 7672 — SMTP Security via Opportunistic DNS-Based Authentication of Named Entities (DANE) Transport Layer Security (TLS)" + url: "https://www.rfc-editor.org/rfc/rfc7672" + publisher: "IETF" +--- + +## What it is + +DANE (DNS-Based Authentication of Named Entities, RFC 6698) lets a domain publish, in DNS, which certificate or public key a TLS service should present. The record type is `TLSA`, and it lives at a name built from the port, the transport and the host. For HTTPS on `www.example.com` that is `_443._tcp.www.example.com`: + +``` +_443._tcp.www.example.com. 3600 IN TLSA 3 1 1 8f1a...c94e +``` + +The three numbers say what is being matched. `3 1 1` (DANE-EE, public key, SHA-256) is the usual choice: "the server's own public key hashes to this value", independent of which certificate authority signed it. `2 1 1` (DANE-TA) pins a trust anchor, such as your CA's intermediate, instead. + +DANE only means anything on top of [DNSSEC](/spec/security/dnssec/). Without signatures, anyone who can forge a DNS answer can forge the `TLSA` record too, so clients ignore `TLSA` records from unsigned zones. + +## Why it matters + +For websites, honestly, not much yet. **No mainstream browser validates DANE.** Chrome, Firefox, Safari and Edge all ignore `TLSA` records and trust the certificate authority system alone. Chrome briefly experimented with DNSSEC-authenticated certificates and dropped the idea, citing the latency of extra DNSSEC lookups and the share of networks that break them; no other browser shipped it. Publishing a `TLSA` record for port 443 therefore changes nothing for a visitor today. That is why this page is `optional`, and why tests that score it (Internet.nl does) are measuring readiness rather than a protection any visitor receives. + +Where DANE does real work is mail. Mail servers deliver to each other over opportunistic STARTTLS, which an attacker can strip or redirect, and there is no browser-style certificate check to fall back on. DANE for SMTP (RFC 7672) lets a receiving domain tell sending servers "always use TLS, and expect this key", and major mail providers do check it. If you run mail on your domain, that is the place to spend DNSSEC and DANE effort first: see [DANE for SMTP on specification.email](https://specification.email/spec/authentication/dane-for-smtp/). + +## How to implement + +If you publish a `TLSA` record for HTTPS anyway, for consistency with a mail setup or for clients that do validate: + +1. **Sign the zone with DNSSEC** and confirm the chain validates. Without this, stop. +2. **Choose the usage.** `3 1 1` pins your server's public key; it survives certificate renewals as long as the key does not change. `2 1 1` pins the CA's intermediate key and survives renewals with that CA. +3. **Generate the record** from the certificate the server actually serves, with a tool such as `ldns-dane` or `hash-slinger`, or by hashing the SubjectPublicKeyInfo with SHA-256. +4. **Roll keys in two steps** (RFC 7671). Publish the `TLSA` record for the new key alongside the old one, wait at least the record's TTL, then switch the server to the new key, then remove the old record. ACME clients that generate a new key at every renewal break `3 1 1` records unless configured to reuse the key or to publish the new record first. + +This site does not ship it for HTTPS: the zone is DNSSEC-signed, but no browser would act on a `_443._tcp` record. For SMTP the `TLSA` records belong to the MX hosts, which here are run by the mail provider, so publishing them is the provider's job. + +## Common mistakes + +- **A `TLSA` record in an unsigned zone.** Validating clients ignore it; it is decoration. +- **Automated certificate renewal with a fresh key each time** and a `3 1 1` record that never changes. The record stops matching on the next renewal. For mail, where senders do check, that is a delivery outage. +- **Expecting browser protection.** A `TLSA` record does not stop a mis-issued certificate from being accepted by a browser. [CAA records](/spec/security/caa-records/) and Certificate Transparency monitoring are what limit mis-issuance on the web. +- **Publishing `TLSA` for the wrong name.** It belongs at `_443._tcp.` plus the exact host name clients connect to, for each host name. + +## Verification + +- `dig +dnssec TLSA _443._tcp.www.example.com` returns the record and the `ad` flag from a validating resolver. +- `openssl s_client -connect www.example.com:443 -dane_tlsa_domain www.example.com -dane_tlsa_rrdata "3 1 1 "` reports a verified DANE match. +- The [Internet.nl website test](https://internet.nl/) includes DANE in its HTTPS section: it checks that a `TLSA` record exists, is DNSSEC-signed, and matches the served certificate. diff --git a/src/content/spec/security/dnssec.md b/src/content/spec/security/dnssec.md index 813be0c4..d50d30f6 100644 --- a/src/content/spec/security/dnssec.md +++ b/src/content/spec/security/dnssec.md @@ -6,7 +6,7 @@ summary: "DNSSEC cryptographically signs DNS records so resolvers can verify the status: optional order: 120 appliesTo: [all] -relatedSlugs: [caa-records, https-tls, hsts, for-sale-dns] +relatedSlugs: [caa-records, https-tls, hsts, for-sale-dns, dane, rpki] updated: "2026-08-08T00:00:00.000Z" sources: - title: "RFC 4033 — DNS Security Introduction and Requirements" @@ -35,7 +35,7 @@ DNSSEC signs; it does not encrypt. The signatures let a resolver detect a tamper Plain DNS is unauthenticated. Anyone on the network path — a coffee-shop router, a compromised ISP, a malicious resolver — can lie about which IP your domain points to, where mail should go, or what TXT records exist. That undermines TLS issuance (because Domain Validation depends on DNS or HTTP that depends on DNS), email security (SPF, DKIM, DMARC live in TXT records), and CAA enforcement. -DNSSEC closes the cache-poisoning and on-path spoofing surface for any resolver that validates. It is also a prerequisite for protocols like DANE, which pin TLS keys in DNS. +DNSSEC closes the cache-poisoning and on-path spoofing surface for any resolver that validates. It is also a prerequisite for [DANE](/spec/security/dane/), which pins TLS keys in DNS; for websites no browser checks it yet, but for mail it is DNSSEC's most useful application. The honest caveat: DNSSEC is operationally tricky. A misconfiguration — a missing `DS` record after a key rollover, an expired signature — takes your whole domain offline for validating resolvers, which is much worse than the average outage. The benefit is real but only worth taking on with tooling that handles rollovers automatically. diff --git a/src/content/spec/security/https-tls.md b/src/content/spec/security/https-tls.md index 00bf71ac..a8ae791a 100644 --- a/src/content/spec/security/https-tls.md +++ b/src/content/spec/security/https-tls.md @@ -6,8 +6,8 @@ summary: "Serve every page over HTTPS using TLS 1.2 or 1.3, redirect plain HTTP status: required order: 10 appliesTo: [all] -relatedSlugs: [hsts, caa-records, content-security-policy, mixed-content] -updated: "2026-08-21T00:00:00.000Z" +relatedSlugs: [tls-configuration, hsts, caa-records, content-security-policy, mixed-content, dane] +updated: "2026-10-15T00:00:00.000Z" sources: - title: "RFC 9846 — The Transport Layer Security (TLS) Protocol Version 1.3" url: "https://www.rfc-editor.org/rfc/rfc9846" @@ -65,6 +65,8 @@ Cipher and protocol checklist: - Hybrid post-quantum key agreement offered on TLS 1.3 — `X25519MLKEM768`. - A complete certificate chain — serve the intermediate, not just the leaf. +The remaining TLS parameters that scanners test one by one (TLS compression, renegotiation, 0-RTT, Extended Main Secret, cipher order, signature hashes, and certificate key size) are covered in [TLS configuration hardening](/spec/security/tls-configuration/). + That third line is stricter than the familiar "forward secrecy only" rule, and the difference is where TLS 1.2 configurations tend to be out of date. RFC 10015 says clients and servers **MUST NOT** offer or select RSA key exchange, static finite-field Diffie-Hellman, _or ephemeral finite-field Diffie-Hellman_ in TLS 1.2 — so `DHE` suites are now disallowed even though they are forward-secret, on the grounds that finite-field groups are slow, historically misconfigured, and no longer worth maintaining alongside the elliptic-curve equivalents. Static `ECDH` is a `SHOULD NOT`. In practice this leaves `ECDHE`, which is what a current Mozilla "Intermediate" configuration already gives you. The fourth line is the newer one. RFC 10024 (August 2026) put three hybrid mechanisms on the standards track — `X25519MLKEM768`, `SecP256r1MLKEM768` and `SecP384r1MLKEM1024` — each pairing the post-quantum ML-KEM with an ordinary elliptic-curve exchange, so a break in either half still leaves the connection secure. `X25519MLKEM768` is the one to offer: the RFC marks it Recommended and describes it as already widely deployed, because browsers and large CDNs shipped it ahead of publication. Its predecessor code points, the draft `X25519Kyber768Draft00` and `SecP256r1Kyber768Draft00`, are obsolete; a server still pinned to those is negotiating something no current client asks for. diff --git a/src/content/spec/security/rpki.md b/src/content/spec/security/rpki.md new file mode 100644 index 00000000..63d88ea2 --- /dev/null +++ b/src/content/spec/security/rpki.md @@ -0,0 +1,75 @@ +--- +title: "RPKI route origin validation" +slug: rpki +category: security +summary: "Make sure the IP prefixes your web, name and mail servers live in are covered by Route Origin Authorisations, so networks that validate routes can reject hijacked announcements. Usually that means choosing a host that signs them." +status: recommended +order: 115 +appliesTo: [all] +relatedSlugs: [dnssec, caa-records, https-tls, ipv6] +updated: "2026-10-15T00:00:00.000Z" +sources: + - title: "RFC 6480 — An Infrastructure to Support Secure Internet Routing" + url: "https://www.rfc-editor.org/rfc/rfc6480" + publisher: "IETF" + - title: "RFC 6811 — BGP Prefix Origin Validation" + url: "https://www.rfc-editor.org/rfc/rfc6811" + publisher: "IETF" + - title: "RFC 9582 — A Profile for Route Origin Authorizations (ROAs)" + url: "https://www.rfc-editor.org/rfc/rfc9582" + publisher: "IETF" + - title: "RFC 9319 (BCP 185) — The Use of maxLength in the Resource Public Key Infrastructure (RPKI)" + url: "https://www.rfc-editor.org/rfc/rfc9319" + publisher: "IETF" +--- + +## What it is + +Traffic finds its way across the internet through BGP, the protocol networks use to announce which IP prefixes they can deliver to. Plain BGP believes whatever it is told: if a network announces a prefix it does not hold, by mistake or on purpose, traffic for that prefix can be pulled towards it. That is a route hijack, and it has repeatedly taken popular sites offline or redirected their traffic. + +The Resource Public Key Infrastructure (RPKI, RFC 6480) lets the holder of an address block publish a signed statement, a Route Origin Authorisation (ROA, RFC 9582), saying which network (identified by its Autonomous System Number, or ASN) may originate a prefix, and how specific an announcement may be. Networks that perform route origin validation (RFC 6811) check each announcement against the published ROAs and label it: + +- **Valid**: a ROA covers the prefix and the origin ASN matches. +- **Invalid**: a ROA covers the prefix, but the origin ASN or the prefix length does not match. Validating networks drop these. +- **Not found**: no ROA covers the prefix. It is accepted, unprotected. + +The misconception worth correcting is that RPKI is something you configure on your website or in your DNS zone. It is not. A ROA is issued by whoever holds the IP address space, through their regional internet registry (RIPE NCC, ARIN, APNIC, LACNIC, AFRINIC). Most site owners rent addresses from a host or CDN and hold no address space at all, so they cannot sign anything themselves. What they control is the choice of host. + +## Why it matters + +A hijacked prefix can black-hole a site, or deliver its visitors to someone else's server. TLS limits the damage when it works, since an attacker without your certificate cannot impersonate an HTTPS site, but a hijack can still take the site offline, intercept plain DNS and SMTP, and in some cases help an attacker pass the domain validation a certificate authority performs. + +ROAs only help if two things are true: the prefix has a ROA, and the networks between your visitors and your server validate. Validation is now widespread among large transit providers and access networks, so the remaining gap is mostly on the publishing side. A prefix with no ROA gets no protection from any of that validation. + +This applies to every server your domain depends on, not just the web server: the authoritative name servers and the mail servers named in your `MX` records each sit in some prefix, and each prefix should be covered. + +## How to implement + +**If you rent hosting, DNS, or mail service** (the usual case): + +1. Find the prefixes your servers sit in. Look up each `A` and `AAAA` address for the site, the name servers, and the `MX` hosts in a routing lookup such as [RIPEstat](https://stat.ripe.net/). +2. Check their RPKI status in the same tool. You want **valid** for every one. +3. If a prefix is not found or invalid, ask the provider to sign ROAs for it. Treat RPKI coverage as a selection criterion when choosing a host, DNS provider, or mail provider; the large ones all sign. + +**If you hold your own address space and announce it yourself:** + +1. Create ROAs in your regional internet registry's portal (or run a delegated RPKI certificate authority) for every prefix you announce, naming the ASN that originates it. +2. Make each ROA match what you actually announce. RFC 9319 recommends setting `maxLength` no longer than the prefix itself, and listing each more-specific prefix explicitly, rather than authorising every possible sub-prefix with a liberal `maxLength`. A loose ROA lets a hijacker announce a more-specific prefix with your ASN as the forged origin and still be judged valid. +3. Create ROAs before you change origins. When moving a prefix to a new upstream or a DDoS-mitigation provider, publish the ROA for the new ASN first, or validating networks will drop the new announcement as invalid. +4. Validate routes you receive, too, if you run BGP: drop invalids. + +This site ships it through its host: the prefixes Cloudflare announces for this site and its name servers are covered by ROAs for Cloudflare's ASN and validate as valid. + +## Common mistakes + +- **Assuming the site's TLS or DNSSEC covers routing.** They protect different layers; neither says anything about who may announce a prefix. +- **Checking only the web server.** The name servers and mail servers are in other prefixes, often run by other providers. +- **A ROA whose length does not cover the announcement.** A ROA for a `/44` with `maxLength` 44 makes a `/48` announced from inside it invalid under that ROA. Either publish a ROA for the `/48` or set the length to match, and prefer the explicit ROA. +- **Moving a prefix to a new network without updating the ROA**, so the new announcement is invalid and validating networks drop it. +- **Letting RPKI certificates or ROAs lapse** in a self-hosted RPKI setup. Expired objects turn valid routes into not found, or worse. + +## Verification + +- Look up each server address on [RIPEstat](https://stat.ripe.net/) and check the RPKI validation status of the announcing prefix and origin ASN. +- Cloudflare's public RPKI test at [isbgpsafeyet.com](https://isbgpsafeyet.com/) checks whether your own access network validates, which is the other half of the system. +- Run the [Internet.nl website test](https://internet.nl/) (and the email test for mail servers). Its RPKI section checks that a ROA exists for each server's prefix and that the announcement is valid. diff --git a/src/content/spec/security/tls-configuration.md b/src/content/spec/security/tls-configuration.md new file mode 100644 index 00000000..1132139d --- /dev/null +++ b/src/content/spec/security/tls-configuration.md @@ -0,0 +1,62 @@ +--- +title: "TLS configuration hardening" +slug: tls-configuration +category: security +summary: "Beyond protocol versions and key exchange: TLS compression off, secure renegotiation only, no unsafe 0-RTT, Extended Main Secret on, server cipher order, SHA-2 signatures, and certificate keys of adequate size." +status: recommended +order: 15 +appliesTo: [all] +relatedSlugs: [https-tls, hsts, compression, dane] +updated: "2026-10-15T00:00:00.000Z" +sources: + - title: "RFC 9325 (BCP 195) — Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)" + url: "https://www.rfc-editor.org/rfc/rfc9325" + publisher: "IETF" + - title: "RFC 5746 — Transport Layer Security (TLS) Renegotiation Indication Extension" + url: "https://www.rfc-editor.org/rfc/rfc5746" + publisher: "IETF" + - title: "RFC 9846 — The Transport Layer Security (TLS) Protocol Version 1.3" + url: "https://www.rfc-editor.org/rfc/rfc9846" + publisher: "IETF" + - title: "RFC 8470 — Using Early Data in HTTP" + url: "https://www.rfc-editor.org/rfc/rfc8470" + publisher: "IETF" +--- + +## What it is + +[HTTPS and TLS](/spec/security/https-tls/) covers the decisions that matter most: TLS 1.2 and 1.3 only, ECDHE key exchange, hybrid post-quantum key agreement, a complete certificate chain. This page is the rest of the checklist: the parameters that tools such as Internet.nl and SSL Labs test one by one, each closing a specific, named attack. + +Most of these settings only matter for TLS 1.2. TLS 1.3 removed compression and renegotiation, always binds the session to the full handshake, and only defines strong cipher suites, so a TLS 1.3-only server passes most of this list by construction. The reason the list still exists is that nearly every public site keeps TLS 1.2 enabled for older clients, and the TLS 1.2 side of the configuration is where the old defaults survive. + +## Why it matters + +Each item below corresponds to an attack that worked against real deployments: CRIME against TLS compression, the 2009 renegotiation injection attack, the triple-handshake attack, replay of early data, and signature forgery against weak hashes. A server that negotiates TLS 1.3 with a current browser is not exposed to most of them, but an attacker chooses the weakest protocol version the server will accept, not the one a typical visitor gets. + +## How to implement + +Start from the Mozilla SSL Configuration Generator's "Intermediate" profile, which covers most of this list, then check each item against your server, since older server versions and hand-edited configurations drift. + +- **TLS compression off.** TLS-level compression leaks the contents of encrypted records through their length, which the CRIME attack used to recover session cookies. RFC 9325 says deployments **SHOULD NOT** support it. TLS 1.3 removed it entirely. This is separate from HTTP compression; see [compression](/spec/performance/compression/) for the related BREACH caveat. +- **Secure renegotiation on, client-initiated renegotiation off.** TLS 1.2 servers must support the `renegotiation_info` extension (RFC 5746), which fixes the 2009 plaintext injection attack; RFC 9325 makes it a **MUST**. Separately, disable renegotiation initiated by the client: it gives nothing to a website and lets a single client force repeated expensive handshakes, a cheap denial-of-service lever. TLS 1.3 has no renegotiation at all. +- **0-RTT off, or only for replay-safe requests.** TLS 1.3 early data (0-RTT) lets a returning client send its first request before the handshake finishes, but that data can be replayed by an attacker (RFC 9846, section 8). RFC 9325 says not to use it unless the application profile makes replay safe. For HTTP, RFC 8470 is that profile: only accept idempotent requests such as `GET` in early data, mark them with the `Early-Data: 1` header towards the origin, and answer `425 Too Early` for anything that changes state. If you cannot enforce that, leave 0-RTT off. +- **Extended Main Secret on (TLS 1.2).** The extension originally specified as "Extended Master Secret" (RFC 7627) binds the session keys to the whole handshake transcript, defeating the triple-handshake attack. RFC 9325 makes support a **MUST** for TLS 1.2. RFC 9846 renamed it "Extended Main Secret" and obsoleted RFC 7627 without changing how it works, so tools may use either name. +- **Server cipher order on TLS 1.2, or only strong suites.** If the server offers a mix of strong and weaker suites, configure it to choose from its own preference list rather than the client's, with AEAD suites (`AES-GCM`, `ChaCha20-Poly1305`) over `ECDHE` first; otherwise an old or misconfigured client can steer the connection to the weakest suite you allow. The alternative is to offer only strong suites, in which case the order no longer matters. That is the approach the Mozilla "Intermediate" profile takes: it turns server preference off because every suite it lists is strong, and Internet.nl accepts that too. On TLS 1.3 every defined suite is strong, so the same reasoning applies. +- **SHA-2 for the key-exchange signature.** In TLS 1.2 the server signs its key exchange parameters, and the hash for that signature is negotiated. SHA-1 and MD5 there **MUST NOT** be used (RFC 9155, cited by RFC 9325); offer SHA-256 or stronger only. +- **Certificate key of adequate size.** RSA keys of at least 2048 bits (RFC 9325 requires this), or ECDSA on P-256 or a larger curve. ECDSA P-256 is smaller and faster; many sites serve both an ECDSA and an RSA certificate and let the client choose. +- **Certificate signed with SHA-256 or stronger.** Publicly trusted CAs no longer issue SHA-1-signed certificates, so this mostly catches internal CAs, very old certificates, and SHA-1 intermediates left in a chain. + +This site inherits these settings rather than configuring them: TLS is terminated by its CDN, Cloudflare, so its handshake parameters are the CDN's. + +## Common mistakes + +- **Copying a TLS configuration from before TLS 1.3** that never disabled compression or client renegotiation because the server's defaults at the time did not. +- **Enabling 0-RTT at the CDN for an application that accepts `POST` requests**, without the origin checking `Early-Data` or returning `425`. +- **Fixing the leaf certificate and forgetting the chain.** A SHA-256 leaf served with a SHA-1 intermediate still fails the signature check. +- **Assuming a good score with a modern browser means the TLS 1.2 side is fine.** Test the server, not one client's view of it. + +## Verification + +- The [Internet.nl website test](https://internet.nl/) checks each item in its HTTPS section: TLS compression, secure and client-initiated renegotiation, 0-RTT, cipher order, key-exchange hash function, certificate public key, certificate signature and Extended Master Secret. +- [Qualys SSL Labs](https://www.ssllabs.com/ssltest/) reports the same parameters, with the handshake each simulated client would get. +- `openssl s_client -connect example.com:443 -tls1_2` shows the negotiated suite and, on recent OpenSSL, whether Extended Master Secret was used.