diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_webrtc_transport.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_webrtc_transport.py | 71 |
1 files changed, 69 insertions, 2 deletions
diff --git a/packages/meshbay-node/tests/test_webrtc_transport.py b/packages/meshbay-node/tests/test_webrtc_transport.py index a4c7f61..dc74752 100644 --- a/packages/meshbay-node/tests/test_webrtc_transport.py +++ b/packages/meshbay-node/tests/test_webrtc_transport.py @@ -33,6 +33,7 @@ from meshbay_common.crypto import ( unwrap_gek, unwrap_gek_aes, ) +from meshbay_common.groupbox import PURPOSE_ACK, PURPOSE_INDEX, unseal from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes from meshbay_common.protocol import MNP TEST_GROUP = "g" @@ -358,13 +359,20 @@ async def test_webrtc_datachannel_file_transfer(sk_node, sk_hub, gek, shared_dir ack = await _handshake_with_gek_proof(channel, received, sk_hub, gek, browser_pc=browser_pc) assert ack["type"] == MNP.HANDSHAKE_ACK + # 1b) The ack's configuration is sealed under the group key (MNP 1.0), and the + # signed handshake transcript names no ack field — so this envelope is the only + # thing authenticating `is_node_admin` and the rest. + config = unseal(gek, PURPOSE_ACK, MNP.HANDSHAKE_ACK, TEST_GROUP, ack) + assert "is_node_admin" in config + assert "is_node_admin" not in ack # 2) Request index channel.send(_pack({"type": MNP.INDEX_SYNC, "v": MNP_VERSION})) idx_msg = await asyncio.wait_for(received.get(), timeout=5.0) assert idx_msg["type"] == MNP.INDEX_SYNC - assert "entries" in idx_msg - assert len(idx_msg["entries"]) > 0 + assert "entries" not in idx_msg, "the index travels in the clear" + payload = unseal(gek, PURPOSE_INDEX, MNP.INDEX_SYNC, TEST_GROUP, idx_msg) + assert len(payload["entries"]) > 0 # 3) Request file chunk entry = next(e for e in indexer.index.entries if e.name == "test.bin") @@ -442,6 +450,65 @@ async def test_webrtc_invalid_jwt_rejected(sk_node, sk_hub, gek, shared_dir): @pytest.mark.asyncio +async def test_webrtc_old_client_is_refused_with_a_code(sk_node, sk_hub, gek, shared_dir): + """ + A version mismatch must present as a refusal, not as a missing field. + + An 0.x client reaching a 1.0 node would otherwise get a `handshake_ack` with + no `enabled_apps` and apply its documented fallback — show every app — and an + `index_sync` with no `entries` it would read as an empty group. Both are + confident wrong answers. The check runs *before* the token, so it costs + nothing and reports the real reason (L2). + """ + hub_pk_pem = _hub_pk_pem(sk_hub) + indexer = DirectoryIndexer(roots=one_root(shared_dir), group_id=TEST_GROUP, + sk_node=sk_node, gek=gek) + await indexer.initial_scan() + + transport = WebRTCTransport( + sk_node=sk_node, hub_pk_pem=hub_pk_pem, gek=gek, + roots=one_root(shared_dir), index=indexer.index, stun_servers=[], + ) + + browser_pc = RTCPeerConnection() + received = asyncio.Queue() + channel = browser_pc.createDataChannel("mnp") + + @channel.on("message") + def on_msg(message): + if isinstance(message, str): + message = message.encode() + received.put_nowait(_unpack(message)) + + offer = await browser_pc.createOffer() + await browser_pc.setLocalDescription(offer) + answer_sdp, _ = await transport.handle_offer( + browser_pc.localDescription.sdp, "peer-old") + await browser_pc.setRemoteDescription( + RTCSessionDescription(sdp=answer_sdp, type="answer")) + await asyncio.sleep(0.5) + + # A perfectly valid token — the refusal must not depend on it, and must not + # be reported as an authorization problem either. + channel.send(_pack({ + "type": MNP.HANDSHAKE, + "v": "0.15", + "token": _make_jwt(sk_hub, groups=[TEST_GROUP]), + "group_id": TEST_GROUP, + "nonce": base64.b64encode(os.urandom(32)).decode(), + })) + + msg = await asyncio.wait_for(received.get(), timeout=5.0) + assert msg["type"] == "error" + # The client matches on the code; the text may be reworded. + assert msg["code"] == "version_too_old" + assert "0.15" in msg["detail"] + + await browser_pc.close() + await transport.close_all() + + +@pytest.mark.asyncio async def test_webrtc_request_before_handshake_rejected(sk_node, sk_hub, gek, shared_dir): """WebRTC DataChannel: request without handshake is rejected.""" hub_pk_pem = _hub_pk_pem(sk_hub) |