iTranscend / iTranscend/thala

Peer handshake is unauthenticated - `PeerId` is self-asserted and the wire is cleartext

Open
#33 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

security
Dominant language
Rust
Stars
1
Forks
0
PR merge metrics
No merged PRs in 30d

Description

The node loads a persisted ed25519 keypair (~/.thala/node-key, 0600) and uses it only to derive a PeerId. Nothing in the codebase ever signs or verifies anything.

Peer connections are raw TcpStream + length-prefixed postcard, and the identity in ConnectionReq is whatever the dialer claims it is:

pub struct ConnectionReq {
    pub peer_id: PeerId,        // self-asserted, never verified
    pub listen_addr: SocketAddr,
    pub message: Option<String>,
    pub capabilities: Capabilities,
}

Consequences:

  • Impersonation. Any TCP client can claim any PeerId - including that of a trusted coordinator - and be recorded in known_peers and gossiped onward to the whole mesh.
  • Forged claims and results. TaskClaim.worker_id and TaskResult.worker_id are unverifiable, so results can be attributed to a node that never ran the task.
  • Capability lies. A peer can advertise arbitrary Capabilities to win placement.
  • No confidentiality or integrity. Cleartext framing, so an on-path attacker can read and rewrite task payloads.

This is a prerequisite for any authorization layer: a capability system can only decide what a proven identity may do, so without key proof there is nothing to root authorization at.

Locations
Fix

A. Challenge–response over the existing TCP framing. Add a ConnectionChallenge/ConnectionProof step: responder sends a random nonce, dialer returns sign(node_key, nonce || peer_id || listen_addr), responder verifies against the public key inlined in the claimed PeerId. Cheaper to land; does not give confidentiality, so pair it with a transport-level story later.

B. Adopt litep2p properly (future). litep2p is already a dependency but only for PeerId and crypto::ed25519. Running connections over its Noise-authenticated transport gets key proof, encryption, and forward secrecy for free, and removes the hand-rolled framing.

Either way: reject the connection on proof failure, and only then insert into known_peers.

Notes

For ed25519, the protobuf-encoded public key is ≤ MAX_INLINE_KEY_LENGTH (42 bytes), so litep2p uses Code::Identity and the public key is inlined in the PeerId - a claimed key can be checked against a PeerId with PeerId::is_public_key(&public_key) with no extra wire field.

Acceptance

A peer that cannot produce a signature over the responder's nonce for its claimed PeerId is refused and never enters known_peers; an integration test asserts a spoofed-PeerId dialer is rejected.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading src/node.rs around handle_peer_connection and handle_peer_message, then inspect ConnectionReq and ConnectionResp in src/message.rs and key handling in src/identity.rs. Choose between challenge–response over the existing framing and adopting litep2p's transport, and confirm the relevant APIs. Done means failed proof rejects the connection before known_peers insertion, with an integration test covering a spoofed PeerId.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
distributed-systems, networking, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.