Peer handshake is unauthenticated - `PeerId` is self-asserted and the wire is cleartext
Nobody has claimed this yet.
- 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 inknown_peersand gossiped onward to the whole mesh. - Forged claims and results.
TaskClaim.worker_idandTaskResult.worker_idare unverifiable, so results can be attributed to a node that never ran the task. - Capability lies. A peer can advertise arbitrary
Capabilitiesto 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
src/node.rs-handle_peer_connection/handle_peer_message, ~330–400src/message.rs-ConnectionReq/ConnectionRespsrc/identity.rs- keypair is derived-from but never used to sign
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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