50 lines
3.7 KiB
Markdown
50 lines
3.7 KiB
Markdown
# Post-quantum readiness scanner
|
|
|
|
`readiness-service.mjs` measures, for one public hostname, whether its TLS edge is ready for post-quantum key exchange. It does not
|
|
estimate: it opens real connections and reports what the server did.
|
|
|
|
1. a TLS 1.3 handshake with the usual classical groups: the baseline (protocol, cipher, group, the certificate and its chain);
|
|
2. a TLS 1.3 handshake offering **only** `X25519MLKEM768` (then `SecP256r1MLKEM768`): does the server know the hybrid key exchange?
|
|
3. a TLS 1.3 handshake offering the hybrid group first, with classical fallbacks: does it **prefer** it? (OpenSSL does not name the
|
|
hybrid group in the ephemeral key information, so the preference is inferred; the method control is that the classical baseline
|
|
comes back with a named group, and without it no claim about preference is made);
|
|
4. a TLS 1.2-only handshake: is a version without post-quantum key exchange still accepted?
|
|
5. an HTTPS `HEAD /` for HSTS.
|
|
|
|
From these it reports the harvest-now-decrypt-later exposure (no post-quantum key exchange: traffic recorded today can be read once a
|
|
cryptographically relevant quantum computer exists), classical certificate authentication (reported, not penalized: no public CA
|
|
issues post-quantum certificates yet), expiry, TLS 1.2, HSTS, a score and concrete recommendations. Every finding has a stable `id`;
|
|
the control plane's planner classifies exactly these ids, and its test derives them from this file.
|
|
|
|
It needs Node.js 24 with OpenSSL 3.5 or later (the ML-KEM groups) and refuses to start otherwise, instead of reporting "no
|
|
post-quantum" about everyone.
|
|
|
|
## As a service
|
|
|
|
PORT=8797 AERE_READINESS_FROM="your label" AERE_READINESS_DIR=/var/lib/aere/readiness node readiness-service.mjs
|
|
curl -s -H 'content-type: application/json' -d '{"domain":"example.com"}' http://127.0.0.1:8797/scan
|
|
|
|
It listens on 127.0.0.1 only; put your own proxy in front. Endpoints: `POST /scan {"domain": ..., "fresh": true?}`, `GET /scan/<domain>`
|
|
(the cached report), `GET /health`. Reports are cached for six hours; a failed scan is not cached.
|
|
|
|
- **No connection to anything that is not proven public.** A hostname that resolves, even partly, to a loopback, private, link-local,
|
|
CGNAT, documentation, multicast or reserved address (IPv4 or IPv6, including mapped and NAT64 forms) gets no connection, and the
|
|
answer does not name its addresses. The connections then go to the address that was checked, so the name is not resolved a second
|
|
time between the check and the connection (`adrese-private.mjs`).
|
|
- **Rate limit per client:** at most 12 scans per minute. The client is the value of the `x-aere-client` header set by the front that
|
|
calls the service (it listens on 127.0.0.1, so only something on the same host can set it), otherwise the connecting address.
|
|
`X-Forwarded-For` is never read: its first element is chosen by the client, and a limit keyed on it could be bypassed by changing it
|
|
(fixed on 2026-09-29).
|
|
- **Bounded work:** at most 4 scans at a time and 32 waiting; past that the answer is `503 busy`, not an unbounded wait.
|
|
|
|
`scaneaza(domain)` and `scaneazaAdresa(domain, address, { port })` can also be imported as functions; the second one has no
|
|
private-address guard (it is for callers that checked the target themselves, such as local tests on 127.0.0.1).
|
|
|
|
## Tests
|
|
|
|
node proba-adrese-private.mjs # the private-address rules, and a local listener that must never be touched by a scan
|
|
|
|
The control plane's tests (`../control-plane/`) also run this scanner against real TLS servers on 127.0.0.1. The rate limit and the
|
|
queue bound are tested by the service that runs it in production (`proba-client-real.mjs` in the Aere Network repository, not
|
|
included here); here they are documented, not measured. No third party has reviewed it.
|