aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-28 15:48:04 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-28 15:48:04 +0200
commit5330896c0e8023053d4cd15961b2ae0482686ca6 (patch)
treee20b35069d4a7a87b1271e9063adb6a5c8e1b157 /packages/meshbay-node/src/meshbay_node/transport
parent0584daa4b77048712c42fa113e77efdd31fc0336 (diff)
downloadmeshbay-5330896c0e8023053d4cd15961b2ae0482686ca6.tar.gz
fix: refuse an unsigned handshake challenge
Every node the 4.0 floor admits signs its challenge, and one without a channel binding could not complete the proof anyway, so a missing signature is refused like a wrong one (browser and QUIC client). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/quic_client.py23
1 files changed, 13 insertions, 10 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/quic_client.py b/packages/meshbay-node/src/meshbay_node/transport/quic_client.py
index 8f3fb74..d548103 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/quic_client.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/quic_client.py
@@ -212,16 +212,19 @@ class QuicChunkClient:
"session — refusing to handshake without channel binding")
binding = quic_binding(self._peer_cert_der)
- # MNP 3.4: a signed challenge proves the node key before we send anything
- # else. A wrong signature is refused; an absent one is an older node.
- if reply.get("sig"):
- try:
- Ed25519PublicKey.from_public_bytes(
- base64.b64decode(reply.get("node_pk", ""))
- ).verify(base64.b64decode(reply["sig"]), challenge_transcript(
- self._group_id, nonce_c, nonce_s, binding))
- except Exception as exc:
- raise ConnectionError(f"Node challenge signature invalid: {exc}") from exc
+ # A signed challenge proves the node key before we send anything else.
+ # Every node we can reach signs (the floor is 4.0, signing is 3.4), and
+ # one without a certificate to bind could not complete the proof anyway,
+ # so a missing signature is refused like a wrong one.
+ if not reply.get("sig"):
+ raise ConnectionError("Node challenge is not signed")
+ try:
+ Ed25519PublicKey.from_public_bytes(
+ base64.b64decode(reply.get("node_pk", ""))
+ ).verify(base64.b64decode(reply["sig"]), challenge_transcript(
+ self._group_id, nonce_c, nonce_s, binding))
+ except Exception as exc:
+ raise ConnectionError(f"Node challenge signature invalid: {exc}") from exc
self._proto._send(self._ctrl_stream, {
"type": MNP.HANDSHAKE_RESPONSE,