diff options
Diffstat (limited to 'packages/meshbay-node')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/quic_server.py | 52 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_quic_transport.py | 9 |
2 files changed, 36 insertions, 25 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 7bb6ce5..ba2d5c5 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py @@ -34,6 +34,7 @@ from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from meshbay_common import MNP_VERSION +from meshbay_common.handshake import HandshakeError, authorize_token from meshbay_common.crypto import ( sign_chunk, pk_to_b64, @@ -193,40 +194,43 @@ class _MNPServerProtocol(QuicConnectionProtocol): self._send(stream_id, {"type": "error", "detail": str(e)}) def _do_handshake_sync(self, stream_id: int, msg: dict) -> None: - token = msg.get("token", "") - group_id = msg.get("group_id", "") - try: - decoded = jwt.decode(token, self._ctx["hub_pk_pem"], algorithms=["EdDSA"]) - except Exception as e: - self._send(stream_id, {"type": "error", "detail": f"Invalid JWT: {e}"}) - self._quic.close() - return - - denylist = self._ctx.get("denylist") - if denylist and denylist.is_denied( - decoded.get("sub", ""), decoded.get("jti", ""), group_id): - self._send(stream_id, {"type": "error", "detail": "Token revoked"}) - self._quic.close() - return + """ + Authorization half of the unified handshake (11.5.4). - if group_id and group_id not in decoded.get("groups", []): - self._send(stream_id, {"type": "error", "detail": "Not a member of this group"}) - self._quic.close() - return + This used to be a second, weaker copy of the WebRTC logic: group_id was + optional (so omitting it skipped the membership check entirely — M1), + node-scoped daemon tokens were accepted as client tokens (M9), and the + checks could drift from the WebRTC path independently. All of that now + comes from meshbay_common.handshake, shared with WebRTC. - if group_id and "groups" in self._ctx and group_id not in self._ctx["groups"]: - self._send(stream_id, {"type": "error", "detail": "Group not hosted on this node"}) + NOT YET DONE — finding C6 remains open on this transport: there is still no + GEK proof here, so a forged or stolen token reaches the node and can inject + chat without holding the group key. The challenge/response and mutual node + proof (quic_binding() is written and unit-tested for exactly this) are the + remaining work in 11.5.4/5/6. + """ + try: + peer = authorize_token( + msg.get("token", ""), + self._ctx["hub_pk_pem"], + group_id=msg.get("group_id", ""), + hosted_groups=self._ctx.get("groups"), + denylist=self._ctx.get("denylist"), + ) + except HandshakeError as refusal: + self._send(stream_id, {"type": "error", "detail": str(refusal)}) self._quic.close() return - self._user_id = decoded["sub"] - self._group_id = group_id + self._user_id = peer.user_id + self._group_id = peer.group_id peers = self._ctx.get("_peers") if peers is not None: peers[self._user_id] = self - log.info("QUIC handshake OK — user=%s group=%s", self._user_id[:8], group_id[:8] if group_id else "none") + log.info("QUIC handshake OK — user=%s group=%s", + self._user_id[:8], self._group_id[:8]) self._send(stream_id, { "type": MNP.HANDSHAKE_ACK, "v": MNP_VERSION, diff --git a/packages/meshbay-node/tests/test_quic_transport.py b/packages/meshbay-node/tests/test_quic_transport.py index 0c1a1cd..c2c2b70 100644 --- a/packages/meshbay-node/tests/test_quic_transport.py +++ b/packages/meshbay-node/tests/test_quic_transport.py @@ -49,7 +49,8 @@ def make_jwt(sk_hub, pk_node_b64, ttl=3600, groups=None): "iss": "test-hub", "sub": "user-001", "pk_user": pk_node_b64, "hub_id": "test-hub", "jti": "test-jti", "iat": now, "exp": now + ttl, - "groups": groups or [], + # group_id is mandatory (M1), so default tokens are members of "g". + "groups": groups if groups is not None else ["g"], }, sk_pem, algorithm="EdDSA") @@ -80,6 +81,7 @@ async def test_quic_chunk_roundtrip(sk_node, sk_hub, gek, shared_dir, tmp_path): host="127.0.0.1", port=19100, jwt_token=token, gek=gek, pk_node_b64=pk_to_b64(sk_node.public_key()), + group_id="g", ) as client: chunk0 = await client.fetch_chunk(entry.id, chunk_index=0) chunk1 = await client.fetch_chunk(entry.id, chunk_index=1) @@ -116,6 +118,7 @@ async def test_quic_fetch_index(sk_node, sk_hub, gek, shared_dir, tmp_path): host="127.0.0.1", port=19101, jwt_token=token, gek=gek, pk_node_b64=pk_to_b64(sk_node.public_key()), + group_id="g", ) as client: wire = await client.fetch_index() recovered = GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek) @@ -220,6 +223,7 @@ async def test_quic_session_resumption(sk_node, sk_hub, gek, shared_dir, tmp_pat async with QuicChunkClient( host="127.0.0.1", port=19104, jwt_token=token, gek=gek, pk_node_b64=pk_b64, + group_id="g", ) as client: wire = await client.fetch_index() assert GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek).count == 2 @@ -233,6 +237,7 @@ async def test_quic_session_resumption(sk_node, sk_hub, gek, shared_dir, tmp_pat host="127.0.0.1", port=19104, jwt_token=token, gek=gek, pk_node_b64=pk_b64, session_ticket=saved_ticket, + group_id="g", ) as client: wire = await client.fetch_index() assert GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek).count == 2 @@ -269,6 +274,7 @@ async def test_quic_denylist_blocks_user(sk_node, sk_hub, gek, shared_dir, tmp_p async with QuicChunkClient( host="127.0.0.1", port=19105, jwt_token=token, gek=gek, pk_node_b64=pk_b64, + group_id="g", ) as client: wire = await client.fetch_index() assert GroupIndex.deserialize(wire, sk_node=sk_node, gek=gek).count == 2 @@ -281,6 +287,7 @@ async def test_quic_denylist_blocks_user(sk_node, sk_hub, gek, shared_dir, tmp_p async with QuicChunkClient( host="127.0.0.1", port=19105, jwt_token=token, gek=gek, pk_node_b64=pk_b64, + group_id="g", ) as client: await client.fetch_index() |