mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 22:26:52 +01:00
Document post-quantum handshakes as hybrid only, and the crossing window
This commit is contained in:
1 parent
dff5c51dd6
commit
1d0281cba5
2 files changed
+72
-84
No files matched your search
+66
-76
@@ -1,16 +1,17 @@
|
|||||||
# Post-quantum handshakes
|
# Post-quantum handshakes
|
||||||
|
|
||||||
Status: draft. Decisions below were made on 2026-10-13 (phase 1) and 2026-10-14 (phase 2); open
|
Status: draft. Decisions below were made on 2026-10-13 and 2026-10-14; open items are listed at
|
||||||
items are listed at the end. Both phases are implemented (see
|
the end. Post-quantum handshakes ship hybrid only, on version 3 certificates (decided
|
||||||
[phase 1](#implementation-phase-1) and [phase 2](#implementation-phase-2)).
|
2026-10-14); see [Implementation](#implementation-certificates-and-the-hybrid-handshake).
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
1. **Confidentiality against harvest-now-decrypt-later.** Traffic recorded today must stay
|
1. **Confidentiality against harvest-now-decrypt-later.** Traffic recorded today must stay
|
||||||
secret after a cryptographically relevant quantum computer exists. This needs a
|
secret after a cryptographically relevant quantum computer exists. This needs a
|
||||||
post-quantum key exchange now.
|
post-quantum key exchange now.
|
||||||
2. **Post-quantum authentication, later.** Needs certificates whose CA signature and host
|
2. **Post-quantum authentication.** Needs certificates whose CA signature and host keys are
|
||||||
keys are post-quantum. This requires reissuing certificates, so it ships second.
|
post-quantum, so certificates have to be reissued or extended. Post-quantum key exchange
|
||||||
|
ships only together with it: there is no post-quantum handshake on version 2 certificates.
|
||||||
3. **Never rely on IP fragmentation.** ML-KEM and ML-DSA do not fit in one packet, so the
|
3. **Never rely on IP fragmentation.** ML-KEM and ML-DSA do not fit in one packet, so the
|
||||||
handshake must carry messages larger than the path MTU itself.
|
handshake must carry messages larger than the path MTU itself.
|
||||||
|
|
||||||
@@ -27,43 +28,30 @@ The only signature in the system stays the CA signature inside the certificate.
|
|||||||
| Today's IX msg1 / msg2 (v2 cert, 151 B) | 252 B / 287 B |
|
| Today's IX msg1 / msg2 (v2 cert, 151 B) | 252 B / 287 B |
|
||||||
|
|
||||||
`crypto/mlkem` is available in Go 1.26; `crypto/mldsa` arrives in Go 1.27, which `go.mod`
|
`crypto/mlkem` is available in Go 1.26; `crypto/mldsa` arrives in Go 1.27, which `go.mod`
|
||||||
requires since phase 2.
|
requires.
|
||||||
|
|
||||||
## Phase 1: ephemeral ML-KEM on today's certificates
|
## An ephemeral-KEM handshake, built and dropped
|
||||||
|
|
||||||
A new handshake subtype, `ix_psk2_mlkem1024` (2), IX-shaped (2 messages; the responder
|
The first design was `ix_psk2_mlkem1024`: classical IX on version 2 certificates with an ephemeral
|
||||||
completes on msg1 as today), using the flynn/noise `psk2` modifier. The Noise protocol name
|
ML-KEM-1024 exchange mixed in through the `psk2` slot, so recorded traffic was protected without
|
||||||
becomes `Noise_IXpsk2_...`, which separates it from the classical subtype.
|
reissuing certificates. It was built, field tested and then dropped on 2026-10-14, before any
|
||||||
|
release. Post-quantum handshakes ship hybrid only, for three reasons:
|
||||||
|
|
||||||
- **msg1**: the initiator generates a fresh ML-KEM-1024 key pair and places the
|
- It left authentication classical, which a deployment wanting quantum safety needs anyway, and
|
||||||
encapsulation key in `NebulaHandshakeDetails.KEMKey` (field 9). msg1's payload is mixed
|
a second post-quantum handshake would be twice the code to review and keep.
|
||||||
into the Noise transcript, so it cannot be stripped or replaced without msg2 failing.
|
- It completes in two messages, so the responder could not tell which of two crossing attempts
|
||||||
With the psk modifier Noise also mixes `e` into the cipher key, so msg1's static key and
|
the initiator kept (see [Crossing attempts](#three-messages-in-the-manager)); on real hosts 5 of
|
||||||
payload are encrypted, though only under a key derived from the public ephemeral.
|
100 first pings failed at 50 ms round trip for that reason.
|
||||||
- **Responder**: after `ReadMessage(msg1)` it encapsulates to that key, obtaining
|
- Its `psk` token came last in msg2, so the responder's static key travelled under classical keys
|
||||||
`(ct, ss)`, and calls `HandshakeState.SetPresharedKey(psk)` before writing msg2, with
|
only.
|
||||||
`psk = HKDF-Extract(SHA-256, salt = "nebula ix_psk2_mlkem1024 v1", ikm = ss || ct)`.
|
|
||||||
The ciphertext is bound in because, unlike the encapsulation key, it is not part of the
|
|
||||||
Noise transcript.
|
|
||||||
- **msg2** on the wire is `ct || noise_msg2`. The ciphertext travels outside the Noise
|
|
||||||
message because the initiator needs `ss` before processing msg2's `psk` token. Any change
|
|
||||||
to `ct` yields a different `ss` (ML-KEM implicit rejection) and the handshake fails.
|
|
||||||
|
|
||||||
Transport keys depend on both the X25519 DH outputs and the ML-KEM shared secret.
|
Subtype 2 and payload field 9 (its encapsulation key) stay reserved. The code is on the
|
||||||
Authentication is unchanged and classical. With a minimal v2 certificate msg1 and msg2 are
|
`pq-handshake` branch for reference.
|
||||||
about 1.84 KB each, so each travels as 2 data and 2 repair fragments of about 936 bytes.
|
|
||||||
A KEM key on the classical subtype, or a missing or malformed one on this subtype, is a fatal
|
|
||||||
protocol error; a forged ciphertext only fails that packet.
|
|
||||||
|
|
||||||
The `psk` token is the last in msg2, so the responder's static key `s` is encrypted under `ee` and
|
## Hybrid IX with certificate v3
|
||||||
`se` only, and a future quantum attacker can read it from a recording; the certificate and indexes
|
|
||||||
follow the psk and stay protected. Identity hiding is a non-goal, and phase 2's KEM-first ordering
|
|
||||||
closes this anyway.
|
|
||||||
|
|
||||||
## Phase 2: hybrid IX with certificate v3
|
Decided on 2026-10-14 and implemented on the `pq-identity` branch (see
|
||||||
|
[Implementation](#implementation-certificates-and-the-hybrid-handshake)).
|
||||||
Phase 2 adds post-quantum authentication. Decisions were made on 2026-10-14; it is implemented on
|
|
||||||
the `pq-identity` branch (see [Implementation](#implementation-phase-2)).
|
|
||||||
|
|
||||||
### Certificate v3
|
### Certificate v3
|
||||||
|
|
||||||
@@ -95,7 +83,7 @@ CertificateV3 ::= SEQUENCE {
|
|||||||
form that replaces the old one, since one v2 CA has one v3 extension.
|
form that replaces the old one, since one v2 CA has one v3 extension.
|
||||||
|
|
||||||
- **The v2 form.** Fields 0 to 3 are a byte exact v2 certificate, signed by the v2 form of the CA.
|
- **The v2 form.** Fields 0 to 3 are a byte exact v2 certificate, signed by the v2 form of the CA.
|
||||||
A v3 holder presents it in classical and phase 1 handshakes, so peers that only speak v2 accept
|
A v3 holder presents it in classical handshakes, so peers that only speak v2 accept
|
||||||
it as they always have, and old parsers never see v3 bytes. A node therefore needs one
|
it as they always have, and old parsers never see v3 bytes. A node therefore needs one
|
||||||
certificate, not a v2 and v3 pair.
|
certificate, not a v2 and v3 pair.
|
||||||
- **Signatures.** Both must verify. The ML-DSA-87 signature covers the classical signature too, so
|
- **Signatures.** Both must verify. The ML-DSA-87 signature covers the classical signature too, so
|
||||||
@@ -197,7 +185,7 @@ flynn/noise) before this can merge.
|
|||||||
|
|
||||||
- **Responder.** After msg2 the responder keeps the handshake pending under its local index, the
|
- **Responder.** After msg2 the responder keeps the handshake pending under its local index, the
|
||||||
multi-message responder the manager lacked before. A repeated msg1 maps to it through the
|
multi-message responder the manager lacked before. A repeated msg1 maps to it through the
|
||||||
answered-msg1 cache and gets msg2 resent with fresh symbols, as in phase 1. msg3's fragments name
|
answered-msg1 cache and gets msg2 resent with fresh symbols. msg3's fragments name
|
||||||
the responder's index, so they reach the pending entry and never the pre-authentication table;
|
the responder's index, so they reach the pending entry and never the pre-authentication table;
|
||||||
its reassembly is capped at 4 KiB. Until msg3 verifies, data for that index is dropped quietly:
|
its reassembly is capped at 4 KiB. Until msg3 verifies, data for that index is dropped quietly:
|
||||||
a RecvError would tear down the initiator's new tunnel. On completing, the responder sends an
|
a RecvError would tear down the initiator's new tunnel. On completing, the responder sends an
|
||||||
@@ -211,22 +199,33 @@ flynn/noise) before this can merge.
|
|||||||
- **Lost confirmation.** A msg3 fragment for a v3 tunnel the responder already completed means the
|
- **Lost confirmation.** A msg3 fragment for a v3 tunnel the responder already completed means the
|
||||||
initiator is still waiting, so the responder confirms again, at most once per retry interval and
|
initiator is still waiting, so the responder confirms again, at most once per retry interval and
|
||||||
only to the tunnel's own remote, never to the fragment's source.
|
only to the tunnel's own remote, never to the fragment's source.
|
||||||
- **Crossing attempts.** The initiator's classical attempt can reach the responder, which
|
- **Crossing attempts.** The initiator alternates, and its first classical attempt follows the
|
||||||
completes it at once, while the hybrid handshake still waits for msg3. If the initiator got the
|
hybrid msg1 anywhere from right away to three retry intervals later (the retry timer lands two
|
||||||
hybrid msg2 first it has committed to hybrid and sends msg3, so the responder takes the hybrid
|
slots ahead on the timer wheel, which moves on whole ticks), so at real round-trip times it
|
||||||
tunnel even though the classical one is newer, rather than rejecting it as too old.
|
often goes out before msg2 is back. Answered, it would complete at once on the responder while
|
||||||
|
the hybrid handshake waits for msg3. So for four retry intervals after answering a peer's new
|
||||||
|
hybrid msg1, the responder leaves that peer's classical msg1s unanswered: the crossing one
|
||||||
|
arrives within the window whatever the round trip, because both take the same path. The
|
||||||
|
window is short so an initiator whose hybrid attempt fails on its own side (one that does not
|
||||||
|
yet trust our v3 CA, during a rollout) still gets a classical answer to a later attempt; a
|
||||||
|
block lasting as long as the hybrid state would refuse it until its handshake timed out, and
|
||||||
|
its next handshake would start with a hybrid msg1 again. A classical msg1 that still crosses,
|
||||||
|
past the window on a very slow path, is answered, but msg3 settles it: the initiator only
|
||||||
|
sends msg3 after committing to hybrid, so the responder takes the hybrid tunnel even though
|
||||||
|
the classical one is newer, rather than rejecting it as too old.
|
||||||
- **A failed attempt.** A post-quantum attempt can fail fatally where the classical one would not,
|
- **A failed attempt.** A post-quantum attempt can fail fatally where the classical one would not,
|
||||||
for example a v3 certificate from a CA the initiator trusts only in its v2 form. The failed
|
for example a v3 certificate from a CA the initiator trusts only in its v2 form. The failed
|
||||||
attempt is dropped and the handshake continues classical. With `require` the handshake ends.
|
attempt is dropped and the handshake continues classical. With `require` the handshake ends.
|
||||||
|
|
||||||
### Rollout with v3
|
### Rollout with v3
|
||||||
|
|
||||||
- A node with a v3 certificate initiates hybrid handshakes in place of phase 1 ones, alternating
|
- `handshakes.pq` only does anything on a node with a v3 certificate. Without one, `initiate`,
|
||||||
with classical as before. It never initiates phase 1, but still answers phase 1 handshakes with
|
`accept` and `sticky` log a warning and are ignored, and `require` is refused at startup and
|
||||||
its v2 form, so a peer with only a v2 certificate gets a classical tunnel from it.
|
on reload, since such a node could reach no one.
|
||||||
- A responder without a v3 certificate drops hybrid msg1s before reassembly.
|
- A responder without a v3 certificate, or whose suite uses another KEM, drops hybrid msg1s
|
||||||
- `handshakes.pq.require` with a v3 certificate means hybrid only, in both directions: phase 1
|
before reassembly, and the initiator falls back to classical.
|
||||||
msg1s are dropped too, so every peer needs a v3 certificate.
|
- `handshakes.pq.require` means hybrid only, in both directions, so every peer needs a v3
|
||||||
|
certificate.
|
||||||
- Post-quantum authentication is required through the CA pool, by not listing the CA's v2 form.
|
- Post-quantum authentication is required through the CA pool, by not listing the CA's v2 form.
|
||||||
A sticky or `require` initiator resists active downgrade of the key exchange; only the CA pool
|
A sticky or `require` initiator resists active downgrade of the key exchange; only the CA pool
|
||||||
resists downgrade of authentication, since a quantum attacker could forge a v2 form.
|
resists downgrade of authentication, since a quantum attacker could forge a v2 form.
|
||||||
@@ -354,7 +353,7 @@ Implemented in `handshake/fragment` (`Sender`, `Assembly`, and the capped `Table
|
|||||||
running Noise or certificate verification.
|
running Noise or certificate verification.
|
||||||
- Before an address is validated, a responder sends at most 3x the bytes it received from
|
- Before an address is validated, a responder sends at most 3x the bytes it received from
|
||||||
it (QUIC's rule). Normal handshakes sit near 1.1-1.2x.
|
it (QUIC's rule). Normal handshakes sit near 1.1-1.2x.
|
||||||
- In phase 2 the initiator retransmits msg3 until any authenticated packet arrives from
|
- The initiator retransmits msg3 until any authenticated packet arrives from
|
||||||
the responder; the responder sends a small confirmation on completion and drops tunnel
|
the responder; the responder sends a small confirmation on completion and drops tunnel
|
||||||
traffic until msg3 verifies.
|
traffic until msg3 verifies.
|
||||||
|
|
||||||
@@ -419,7 +418,7 @@ does not downgrade.
|
|||||||
`handshakes.pq.initiate`, `handshakes.pq.accept`. `handshakes.pq.repair_delay` (default
|
`handshakes.pq.initiate`, `handshakes.pq.accept`. `handshakes.pq.repair_delay` (default
|
||||||
10ms) sets how long repair fragments trail the data fragments.
|
10ms) sets how long repair fragments trail the data fragments.
|
||||||
- `handshakes.pq.require` makes every handshake PQ: it implies `initiate` and `accept`,
|
- `handshakes.pq.require` makes every handshake PQ: it implies `initiate` and `accept`,
|
||||||
never offers a classical attempt, and drops classical msg1s.
|
never offers a classical attempt, and drops classical msg1s. It needs a v3 certificate.
|
||||||
- Old nodes silently drop unknown subtypes, so discovery is speculative: an initiator with
|
- Old nodes silently drop unknown subtypes, so discovery is speculative: an initiator with
|
||||||
`initiate` enabled alternates PQ and classical attempts on successive timer retries,
|
`initiate` enabled alternates PQ and classical attempts on successive timer retries,
|
||||||
keeping both pending, and the first to complete wins. A path nothing has been sent over
|
keeping both pending, and the first to complete wins. A path nothing has been sent over
|
||||||
@@ -444,14 +443,14 @@ mean burst length in packets. First-attempt success for the whole handshake with
|
|||||||
|
|
||||||
| Handshake | Loss | Burst 1 | Burst 2 | Burst 4 | Burst 8 |
|
| Handshake | Loss | Burst 1 | Burst 2 | Burst 4 | Burst 8 |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| phase 1 (2, 2) no FEC | 5% | 81.4% | 85.6% | 87.9% | 89.0% |
|
| two messages (2, 2) no FEC | 5% | 81.4% | 85.6% | 87.9% | 89.0% |
|
||||||
| phase 1 (2, 2) FEC | 5% | 99.9% | 99.4% | 99.3% | 98.6% |
|
| two messages (2, 2) FEC | 5% | 99.9% | 99.4% | 99.3% | 98.6% |
|
||||||
| phase 1 (2, 2) FEC | 10% | 99.3% | 97.5% | 97.4% | 96.3% |
|
| two messages (2, 2) FEC | 10% | 99.3% | 97.5% | 97.4% | 96.3% |
|
||||||
| phase 2 (7, 9, 2) no FEC | 5% | 39.8% | 57.5% | 70.4% | 77.7% |
|
| hybrid (7, 9, 2) no FEC | 5% | 39.8% | 57.5% | 70.4% | 77.7% |
|
||||||
| phase 2 (7, 9, 2) FEC | 5% | 99.6% | 94.2% | 90.4% | 88.8% |
|
| hybrid (7, 9, 2) FEC | 5% | 99.6% | 94.2% | 90.4% | 88.8% |
|
||||||
| phase 2 (7, 9, 2) FEC | 10% | 95.9% | 84.6% | 79.0% | 77.3% |
|
| hybrid (7, 9, 2) FEC | 10% | 95.9% | 84.6% | 79.0% | 77.3% |
|
||||||
|
|
||||||
With fresh repair symbols, phase 2 completes within two attempts at least 99.1% of the time
|
With fresh repair symbols, the hybrid handshake completes within two attempts at least 99.1% of the time
|
||||||
in every scenario above, and within three at least 99.99%.
|
in every scenario above, and within three at least 99.99%.
|
||||||
|
|
||||||
## Prior art
|
## Prior art
|
||||||
@@ -471,10 +470,8 @@ in every scenario above, and within three at least 99.99%.
|
|||||||
- **TFTP, NFS duplicate request cache, memcached UDP, IP reassembly bugs**: retransmit
|
- **TFTP, NFS duplicate request cache, memcached UDP, IP reassembly bugs**: retransmit
|
||||||
storms, ID-wrap misassembly, reflection amplification, and allocation from claimed sizes.
|
storms, ID-wrap misassembly, reflection amplification, and allocation from claimed sizes.
|
||||||
|
|
||||||
## Implementation (phase 1)
|
## Implementation: fragment transport and manager
|
||||||
|
|
||||||
- `handshake`: the `ix_psk2_mlkem1024` subtype (`patterns.go`, `kem.go`), the `KEMKey`
|
|
||||||
payload field, and the psk2 plumbing in `Machine`.
|
|
||||||
- `handshake/fec`, `handshake/fragment`: the code and the fragment transport.
|
- `handshake/fec`, `handshake/fragment`: the code and the fragment transport.
|
||||||
- `handshake_manager_pq.go` and the manager:
|
- `handshake_manager_pq.go` and the manager:
|
||||||
- **Initiator.** Attempts alternate PQ and classical, starting with PQ (sticky peers get
|
- **Initiator.** Attempts alternate PQ and classical, starting with PQ (sticky peers get
|
||||||
@@ -492,16 +489,16 @@ in every scenario above, and within three at least 99.99%.
|
|||||||
- The pre-authentication table is capped at 4 MiB, 1024 entries, 4 per source (IPv4 /32,
|
- The pre-authentication table is capped at 4 MiB, 1024 entries, 4 per source (IPv4 /32,
|
||||||
IPv6 /64, or relay channel), 16 KiB messages and a 3 s idle timeout. Cookies are
|
IPv6 /64, or relay channel), 16 KiB messages and a 3 s idle timeout. Cookies are
|
||||||
demanded from half full.
|
demanded from half full.
|
||||||
- Tests: Noise-level KEM tests, codec and fragment tests and fuzzers, manager unit tests
|
- Tests: codec and fragment tests and fuzzers, manager unit tests
|
||||||
for the response budget and rate limit, and e2e tests for a clean handshake, loss of
|
for the response budget and rate limit, and e2e tests for a clean handshake, loss of
|
||||||
data fragments in both directions, fallback to a peer that does not accept PQ, a lost
|
data fragments in both directions, fallback to a peer that does not accept PQ, a lost
|
||||||
msg2 recovered by fresh symbols, sticky mode under an attacker dropping PQ packets,
|
msg2 recovered by fresh symbols, sticky mode under an attacker dropping PQ packets,
|
||||||
relays, index cleanup on completion, fallback and timeout, and a responder flooded from
|
relays, index cleanup on completion, fallback and timeout, and a responder flooded from
|
||||||
spoofed sources demanding and accepting a cookie. Unit tests cover cookie binding and
|
spoofed sources demanding and accepting a cookie. Unit tests cover cookie binding and
|
||||||
rotation, the never-amplify rule, the initiator's rate limited resend, and reload. ML-KEM and HKDF also
|
rotation, the never-amplify rule, the initiator's rate limited resend, and reload. ML-KEM also
|
||||||
pass under `GOEXPERIMENT=boringcrypto` and `GODEBUG=fips140=on`.
|
passes under `GOEXPERIMENT=boringcrypto` and `GODEBUG=fips140=on`.
|
||||||
|
|
||||||
## Implementation (phase 2)
|
## Implementation: certificates and the hybrid handshake
|
||||||
|
|
||||||
- `cert`: `cert_v3.go` (format, `V2Form`, `ExtendToV3`, `SignPQ`), `pq_suite.go` (`PQSuite` and
|
- `cert`: `cert_v3.go` (format, `V2Form`, `ExtendToV3`, `SignPQ`), `pq_suite.go` (`PQSuite` and
|
||||||
the suite table), `SignHybridWith` and the composite `Sign` path, the CA pool's v3 index, suite
|
the suite table), `SignHybridWith` and the composite `Sign` path, the CA pool's v3 index, suite
|
||||||
@@ -512,8 +509,9 @@ in every scenario above, and within three at least 99.99%.
|
|||||||
`Machine.PeerCert` for a responder that has to act before completion.
|
`Machine.PeerCert` for a responder that has to act before completion.
|
||||||
- `pki.go` loads a v3 certificate with its composite key and derives the v2 form; messages after
|
- `pki.go` loads a v3 certificate with its composite key and derives the v2 form; messages after
|
||||||
the handshake treat v3 tunnels as v2 (`protocolVersion`).
|
the handshake treat v3 tunnels as v2 (`protocolVersion`).
|
||||||
- `handshake_manager_hybrid.go`: pending responders, confirmation and reconfirmation, and the
|
- `handshake_manager_hybrid.go`: pending responders, confirmation and reconfirmation, the
|
||||||
initiator's msg3 resends and teardown; `outside.go` keeps quiet about pending responder indexes.
|
initiator's msg3 resends and teardown, and the crossing window; `outside.go` keeps quiet about
|
||||||
|
pending responder indexes.
|
||||||
- `nebula-cert`: `ca`, `keygen` and `sign` take `-version 3`, `keygen -in-key` extends an existing
|
- `nebula-cert`: `ca`, `keygen` and `sign` take `-version 3`, `keygen -in-key` extends an existing
|
||||||
host key, `sign -version 2` with a v3 CA signs as its v2 form, and `pq-extend` upgrades v2
|
host key, `sign -version 2` with a v3 CA signs as its v2 form, and `pq-extend` upgrades v2
|
||||||
certificates in place, keeping an encrypted CA key encrypted. `ca`, `keygen` and `pq-extend` of a
|
certificates in place, keeping an encrypted CA key encrypted. `ca`, `keygen` and `pq-extend` of a
|
||||||
@@ -538,21 +536,13 @@ in every scenario above, and within three at least 99.99%.
|
|||||||
it times out, and the Machine reports it as recoverable. Blocks publishing the module; the fix is
|
it times out, and the Machine reports it as recoverable. Blocks publishing the module; the fix is
|
||||||
a deferred rollback that also restores `k`, `n` and `hasK`. Smaller pre-publish items are in
|
a deferred rollback that also restores `k`, `n` and `hasK`. Smaller pre-publish items are in
|
||||||
[DefinedNet/noise#3](https://github.com/DefinedNet/noise/issues/3).
|
[DefinedNet/noise#3](https://github.com/DefinedNet/noise/issues/3).
|
||||||
- The initiator's first classical retry can fire 0-100 ms after the post-quantum attempt, so at
|
|
||||||
real round-trip times it often crosses it. For hybrid, msg3 settles which tunnel the responder
|
|
||||||
keeps, but the crossing classical tunnel lingers on the responder until it is found dead. The
|
|
||||||
ephemeral-KEM handshake has no msg3: the responder keeps the newer classical tunnel while the
|
|
||||||
initiator keeps the KEM one, and replies go to a released index (field test: 5 of 100 first
|
|
||||||
pings failed at 50 ms round trip, no loss). Options: no classical turn until one retry interval
|
|
||||||
after the post-quantum attempt, and/or complete a losing attempt's late msg2 only to send it a
|
|
||||||
CloseTunnel.
|
|
||||||
|
|
||||||
Field tested on real hosts on 2026-10-14 (`pq-lab/2026-10-14-hybrid/`, untracked): LAN median 3.2 ms
|
Field tested on real hosts on 2026-10-14 (`pq-lab/2026-10-14-hybrid/`, untracked): LAN median 3.2 ms
|
||||||
against 1.4 ms classical, +2.0 ms at 50 ms round trip, no fallbacks with `require` under loss.
|
against 1.4 ms classical, +2.0 ms at 50 ms round trip, no fallbacks with `require` under loss.
|
||||||
|
|
||||||
The cryptographic review of 2026-10-14 found no break in the composition or the certificate
|
The cryptographic review of 2026-10-14 found no break in the composition or the certificate
|
||||||
chain. Its outcomes are folded in above: deterministic ML-DSA signing, the fingerprint guidance,
|
chain. Its outcomes are folded in above: deterministic ML-DSA signing, the fingerprint guidance,
|
||||||
the `skem` oracle note, the psk2 note, the pending-responder exposure and the `sticky` default.
|
the `skem` oracle note, the psk2 note (moot now that handshake is dropped), the pending-responder
|
||||||
|
exposure and the `sticky` default.
|
||||||
|
|
||||||
Accepted as is: a tunnel that came up classical between two v3 holders (say, the hybrid attempt
|
Accepted as is: a tunnel that came up classical between two v3 holders (say, the hybrid attempt
|
||||||
was dropped) stays classical until it rehandshakes for another reason. `require`, `sticky` and a
|
was dropped) stays classical until it rehandshakes for another reason. `require`, `sticky` and a
|
||||||
|
|||||||
+6
-8
@@ -425,12 +425,11 @@ logging:
|
|||||||
# after receiving the response for lighthouse queries
|
# after receiving the response for lighthouse queries
|
||||||
#trigger_buffer: 64
|
#trigger_buffer: 64
|
||||||
|
|
||||||
# pq enables handshakes that mix an ephemeral ML-KEM-1024 key exchange into the session keys, protecting
|
# pq enables hybrid handshakes: ML-KEM-1024 alongside the classical key exchange, so recorded traffic stays
|
||||||
# recorded traffic against a future quantum computer. These handshakes are too large for one packet and travel
|
# secret against a future quantum computer, and authentication by the version 3 certificates' ML-KEM-1024 keys.
|
||||||
# as fragments with forward error correction.
|
# Both ends need a version 3 certificate in pki.cert; without one these settings are ignored with a warning, and
|
||||||
# With a version 3 certificate in pki.cert they are hybrid handshakes, also authenticated post-quantum with the
|
# require is refused. These handshakes are too large for one packet and travel as fragments with forward error
|
||||||
# certificates' ML-KEM-1024 keys, and need a version 3 certificate on both ends. Without one, authentication is
|
# correction.
|
||||||
# unchanged.
|
|
||||||
#pq:
|
#pq:
|
||||||
# initiate starts post-quantum handshakes. Attempts alternate between post-quantum and classical until one is
|
# initiate starts post-quantum handshakes. Attempts alternate between post-quantum and classical until one is
|
||||||
# answered, so peers that do not support them still connect.
|
# answered, so peers that do not support them still connect.
|
||||||
@@ -438,8 +437,7 @@ logging:
|
|||||||
# accept answers post-quantum handshakes from peers.
|
# accept answers post-quantum handshakes from peers.
|
||||||
#accept: false
|
#accept: false
|
||||||
# require makes every handshake post-quantum: it implies initiate and accept, never offers a classical handshake
|
# require makes every handshake post-quantum: it implies initiate and accept, never offers a classical handshake
|
||||||
# and refuses classical handshakes from peers. Only enable it once every host can speak post-quantum handshakes.
|
# and refuses classical handshakes from peers. Only enable it once every host has a version 3 certificate.
|
||||||
# With a version 3 certificate only hybrid handshakes are made or answered, so every peer needs one too.
|
|
||||||
#require: false
|
#require: false
|
||||||
# sticky never falls back to a classical handshake with a peer that has completed a post-quantum one since
|
# sticky never falls back to a classical handshake with a peer that has completed a post-quantum one since
|
||||||
# nebula started, which defeats an attacker who drops post-quantum handshakes to force a downgrade.
|
# nebula started, which defeats an attacker who drops post-quantum handshakes to force a downgrade.
|
||||||
|
|||||||
Reference in new issue
Block a user