aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport/quic_server.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-23 17:14:26 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-23 17:14:26 +0200
commit339cb427f886a0177014126bb684335837eff067 (patch)
tree5f79dc0df617be66287a06fc4f0c5dcc61ceb167 /packages/meshbay-node/src/meshbay_node/transport/quic_server.py
parentcd85808c13926c89a97987d320ac26391eae3267 (diff)
downloadmeshbay-339cb427f886a0177014126bb684335837eff067.tar.gz
feat: the node signs its handshake challenge (MNP 3.4)
node_pk in handshake_challenge is now signed over the channel binding and both nonces, so a client can check the node key before a join rather than only at the ack. Both transports; the browser and the QUIC client refuse a wrong signature and treat an absent one as an older node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport/quic_server.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/quic_server.py21
1 files changed, 17 insertions, 4 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py
index 036574b..96cd752 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py
@@ -41,6 +41,7 @@ from meshbay_common.handshake import (
ROLE_NODE,
HandshakeError,
authorize_token,
+ challenge_transcript,
check_version,
handshake_transcript,
make_proof,
@@ -313,11 +314,23 @@ class _MNPServerProtocol(QuicConnectionProtocol):
# Decoded but NOT authenticated: authentication is the GEK proof below.
self._pending = peer
self._gek_challenge = os.urandom(NONCE_LEN)
+ # The same announcement and signature as WebRTC's challenge (MNP 3.4),
+ # so the two transports stay one handshake. The binding is the node's
+ # certificate, which is known here as it is at the proof.
+ sig = {}
+ cert = self._ctx.get("server_cert_der")
+ if cert:
+ transcript = challenge_transcript(
+ peer.group_id, self._nonce_client, self._gek_challenge,
+ quic_binding(cert))
+ sig = {"sig": base64.b64encode(self._ctx["sk_node"].sign(transcript)).decode()}
self._send(stream_id, {
- "type": MNP.HANDSHAKE_CHALLENGE,
- "v": MNP_VERSION,
- "v_min": MNP_MIN_SUPPORTED,
- "nonce": base64.b64encode(self._gek_challenge).decode(),
+ "type": MNP.HANDSHAKE_CHALLENGE,
+ "v": MNP_VERSION,
+ "v_min": MNP_MIN_SUPPORTED,
+ "nonce": base64.b64encode(self._gek_challenge).decode(),
+ "node_pk": pk_to_b64(self._ctx["sk_node"].public_key()),
+ **sig,
})
def _do_handshake_response_sync(self, stream_id: int, msg: dict) -> None: