Просмотр исходного кода

Fold the 2026-10-14 crypto review into the spec: deterministic signing, which fingerprint to block, the skem oracle, the psk2 static key, pending responders, the sticky default and the fork's rollback gap

JackDoan 6 дней назад
Родитель
Сommit
5c5d6e3314
2 измененных файлов с 61 добавлено и 12 удалено
  1. 9 7
      CHANGELOG.md
  2. 52 5
      docs/pq-handshake.md

+ 9 - 7
CHANGELOG.md

@@ -10,16 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - Version 3 certificates and post-quantum hybrid handshakes (`ix_hybrid_mlkem1024`). A version 3 certificate is a
-  version 2 certificate extended with an ML-KEM-1024 key (hosts) or an ML-DSA-87 key (CAs) and an ML-DSA-87 CA
-  signature, and a node holding one still presents the version 2 certificate it extends to peers that need it.
-  `nebula-cert pq-extend` upgrades existing CAs and hosts in place. See `docs/pq-handshake.md`.
+  version 2 certificate extended with an ML-KEM-1024 key (hosts) or an ML-DSA-87 key (CAs) and a deterministic
+  ML-DSA-87 CA signature, and a node holding one still presents the version 2 certificate it extends to peers that
+  need it. `nebula-cert pq-extend` upgrades existing CAs and hosts in place, and extending a certificate twice gives
+  the same certificate. See `docs/pq-handshake.md`.
 
 ### Changed
 
-- A version 3 certificate has two fingerprints: its own, and that of the version 2 certificate it extends. Each
-  identifies it in logs and to `pki.blocklist`. Blocking the v2 fingerprint blocks both forms, but blocking only the
-  v3 fingerprint leaves the v2 form usable in classical handshakes. **Operators must list and block both
-  fingerprints**; `nebula-cert print` shows them as `fingerprint` and `v2Fingerprint`.
+- A version 3 certificate has two fingerprints: its own, and that of the version 2 certificate it extends. Both
+  identify it in logs and to `pki.blocklist`. **To block a version 3 certificate, block its v2 fingerprint**: the
+  pool checks it for the v3 certificate and for its v2 form alike, so that one entry covers both forms. Blocking only
+  the v3 fingerprint leaves the v2 form usable in classical handshakes. `nebula-cert print` leads with both
+  fingerprints and says which one to block; in JSON they are `fingerprint` and `v2Fingerprint`.
 - Firewall `ca_sha` rules match a version 3 peer by either fingerprint of its CA.
 
 ### Fixed

+ 52 - 5
docs/pq-handshake.md

@@ -55,6 +55,11 @@ about 1.84 KB each, so each travels as 2 data and 2 repair fragments of about 93
 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
+`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
 
 Phase 2 adds post-quantum authentication. Decisions were made on 2026-10-14; it is implemented on
@@ -85,12 +90,18 @@ CertificateV3 ::= SEQUENCE {
 - **Signatures.** Both must verify. The ML-DSA-87 signature covers the classical signature too, so
   neither signature can be swapped out (which also makes P256 low/high-s alternates moot for v3).
   If ML-DSA fell classically and someone swapped `pqPublicKey`, the hybrid handshake still needs
-  the classical static key, which only the classical signature covers.
+  the classical static key, which only the classical signature covers. The ML-DSA-87 signature is
+  deterministic (FIPS 204 section 3.4), so extending the same v2 certificate with the same key twice
+  gives the same v3 certificate and fingerprint: `pq-extend` is idempotent. The hedged variant
+  guards signers an attacker can fault physically, which a CA key in a file is not, and the Ed25519
+  signature beside it is deterministic too.
 - **Issuer and fingerprints.** The details are v2-shaped, so the issuer is the fingerprint of the
   CA's v2 form. A v3 certificate's own fingerprint is the SHA-256 of its whole encoding and differs
-  from its v2 form's. Both identify it: the blocklist checks both, and operators must list both,
-  since blocking only the v3 fingerprint leaves the v2 form usable in classical handshakes. Firewall
-  `ca_sha` rules match a v3 peer by either fingerprint of its CA.
+  from its v2 form's. Both identify it. To block a v3 certificate, block its v2 fingerprint: the
+  pool checks it for the v3 certificate and for its v2 form alike, so one entry covers both forms,
+  while blocking only the v3 fingerprint leaves the v2 form usable in classical handshakes.
+  `nebula-cert print` leads with both fingerprints and says which one to block. Firewall `ca_sha`
+  rules match a v3 peer by either fingerprint of its CA.
 - **CA pool.** A v3 CA verifies only v3 certificates. To accept v2 certificates as well the pool
   must also list the CA's v2 form. Dropping the v2 form is how a node requires post-quantum
   authentication; there is no separate setting. Two different v3 extensions of one v2 CA are
@@ -143,6 +154,13 @@ design rationale and security argument), with ML-KEM-1024 supplied by nebula:
   first packet under the new keys decrypts (it depends on msg3's `skem`). Neither side needs a
   signature. Knowing only a peer's classical private key gets an impersonator nowhere: as an
   initiator it cannot read msg2, as a responder it cannot read msg3.
+- **The two static ML-KEM keys see different ciphertexts.** msg2's `skem` ciphertext is encrypted
+  under a key derived from the `ekem` secret, which whoever sent msg2 chose, so anyone who can
+  address a pending initiator's index can have the initiator decapsulate a ciphertext of their
+  choosing with its static key. That is the IND-CCA setting ML-KEM is designed for, and
+  `crypto/mlkem` is constant time. The responder decrypts msg3's `skem` ciphertext under the keys of
+  a completed msg2 before decapsulating, so it only ever decapsulates ciphertexts the genuine
+  initiator sealed.
 - v3 certificates go with this subtype and nothing else: a v3 certificate on any other subtype, or
   a v2 one on this one, is a protocol error.
 
@@ -193,6 +211,9 @@ flynn/noise) before this can merge.
 - 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
   resists downgrade of authentication, since a quantum attacker could forge a v2 form.
+- `sticky` stays off by default for v3 holders too (decided 2026-10-14): a peer re-issued back to a
+  v2-only certificate would otherwise be unreachable until this process restarts. Deployments that
+  want downgrade resistance turn on `sticky` or `require`, or drop the CA's v2 form from the pool.
 
 ## Fragment transport
 
@@ -337,6 +358,21 @@ WireGuard's mac2, QUIC Retry and the SCTP state cookie:
 
 A CA-keyed pre-filter MAC was considered and rejected: networks commonly trust many CAs.
 
+#### Pending hybrid responders
+
+A hybrid responder that answered msg1 keeps a pending entry, keyed by its local index, until
+msg3 arrives or the handshake timeout (5.5 s by default) passes. msg1 is cleartext and nothing
+covers `e` or `Time`, so from one captured v3 msg1 an attacker can mint distinct valid msg1s
+with a fresh `e` and a bumped `Time`. Each misses the answered-msg1 cache, costs the responder
+the whole msg1 (two signature verifications, two encapsulations, four DH operations, the
+certificate checks) and a pending entry, and the fragment table stays near empty because a
+complete burst arrives together, so cookies are never demanded and spoofed sources work.
+Pending entries are not bounded per peer or in total. Accepted as is (2026-10-14): the per-msg1
+work is the same class as classical IX's pre-authentication cost, the expiry bounds the state in
+time and the fragment table bounds bytes in flight. If it needs tightening, cap pending
+responders per certificate fingerprint and in total, and count them toward the cookie
+threshold.
+
 #### Injected symbols
 
 The decoder keeps the first K distinct symbols it sees, so one forged symbol in those K
@@ -467,9 +503,20 @@ in every scenario above, and within three at least 99.99%.
 - Publish DefinedNet/noise and drop the `replace` in `go.mod`, once Nate says the fork is ready to
   ship. The classical-only responder `e` is on the local clone's `drop-responder-ekem-key` branch
   (b4a892b), not yet in DefinedNet/noise.
-- Cryptographic review of the hybrid composition and of the v3 certificate signatures.
+- DefinedNet/noise `ReadMessage` returns `ErrShortMessage` without rolling back the symmetric
+  state ([DefinedNet/noise#2](https://github.com/DefinedNet/noise/issues/2), inherited from
+  flynn/noise). A message that fails a length check after a key-mixing token leaves `ck` and `h`
+  advanced, so the genuine message can never be read: one spoofed short packet to a pending
+  initiator's index, which msg1 carries in the clear in every IX variant, kills that attempt until
+  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
+  [DefinedNet/noise#3](https://github.com/DefinedNet/noise/issues/3).
 - Field test of hybrid handshakes on real hosts, as was done for phase 1.
 
+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,
+the `skem` oracle note, the psk2 note, 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
 was dropped) stays classical until it rehandshakes for another reason. `require`, `sticky` and a
 CA pool without v2 forms cover deployments that need more. PKCS#11 keys do not support v3.