summaryrefslogtreecommitdiffstats
path: root/devel-phases-next.md
diff options
context:
space:
mode:
Diffstat (limited to 'devel-phases-next.md')
-rw-r--r--devel-phases-next.md40
1 files changed, 39 insertions, 1 deletions
diff --git a/devel-phases-next.md b/devel-phases-next.md
index 687e4f1..902f01d 100644
--- a/devel-phases-next.md
+++ b/devel-phases-next.md
@@ -614,7 +614,45 @@ phase reduces the node to two paths and brings both to the same standard.
|---|---|---|---|
| 11.5.4 | Extract `meshbay_common/handshake.py`: JWT verify → `scope == "user"` → denylist → **mandatory** `group_id` in claims → group hosted → GEK challenge → proof verify → ack | **C6**, M1, M9 | Single implementation; `webrtc_server.py` and `quic_server.py` contain no JWT logic of their own |
| 11.5.5 | Both transports call it; test parametrized over `[webrtc, quic]` | C6 | A test that adds a step to the handshake fails for any transport that skips it |
-| 11.5.6 | **Spike:** channel binding for QUIC. No DTLS fingerprint exists — bind to the QUIC server certificate hash as the analogue (`sha256(server_cert) ‖ sha256(client_cert)`); prefer an RFC 5705 TLS exporter if `aioquic` can expose one | C6/NS5 | QUIC handshake proof is bound to the connection, not replayable across connections |
+| 11.5.6 | **Spike DONE 2026-08-13 — see findings below.** Channel binding for QUIC | C6/NS5 | QUIC handshake proof is bound to the connection, not replayable across connections |
+
+#### 11.5.6 spike results (aioquic 1.3.0)
+
+**No RFC 5705 exporter.** `aioquic.tls.Context` has no `export_keying_material`, so the
+preferred anchor is unavailable.
+
+**Certificate access is asymmetric and partly private:**
+
+| Side | Path to the server certificate | API status |
+|---|---|---|
+| Server | `tls.certificate` | public attribute |
+| Client | `tls._peer_certificate` | **private** — set by `_set_peer_certificate()` |
+
+`QuicConnection` exposes no `tls`/`cert` attribute either, so the client's route is
+`protocol._quic.tls._peer_certificate` — two levels of private API.
+
+**The risk this creates.** Binding a security check to a private attribute means an
+aioquic upgrade can remove it silently. A channel binding that silently becomes
+unavailable is the worst failure mode: 11.5.21 already established that the handshake
+must *refuse* rather than degrade, so a rename would turn every QUIC connection into a
+hard failure — noisy, but only if the refusal path is right. If it were ever made
+tolerant, it would turn into a silent loss of MitM detection.
+
+**Options for the implementer, in order of preference:**
+
+1. **Certificate hash via the private attribute, guarded.** Pin `aioquic` in
+ `pyproject.toml`, and add a test that asserts `_peer_certificate` is reachable and
+ non-None on a live connection — so an upgrade fails CI rather than production. Keep
+ `make_proof()` refusing an empty binding.
+2. **Bind to `pk_node` instead of the channel.** For QUIC the MitM story differs from
+ WebRTC: signaling is not hub-relayed, and the client already learns `pk_node` from the
+ hub. The C3 mutual proof (node signs the transcript with `sk_node`) may be sufficient
+ connection authentication on its own — but note the QUIC client currently does **not**
+ verify the TLS certificate (`verify_mode` disabled, identity checked at the MNP layer),
+ so this option must be paired with pinning, or the TLS layer authenticates nobody.
+3. **Upstream an exporter.** Correct long-term answer, wrong timescale for 11.5.
+
+Recommendation: option 1 with the guard test, and open option 3 upstream.
### C — Mutual authentication