Skip to main content

decdn_node/dht/
auth.rs

1//! The authenticated-identity trust boundary for the DHT handler.
2//!
3//! [`AuthenticatedNodeId`] wraps a [`NodeId`] that originated from a live QUIC
4//! connection's `remote_id()` — i.e. an identity iroh's handshake has bound to
5//! the connection, not one a peer asserted in a wire message body. It is
6//! deliberately the *only* way to obtain a `NodeId` the handler trusts as "who
7//! is actually on the other end".
8//!
9//! # Why this type exists
10//!
11//! ADR 022's STORE/FIND flows hinge on distinguishing two superficially
12//! identical 32-byte values:
13//!
14//! - the **authenticated** peer id (`conn.remote_id()`), and
15//! - **wire-provided** ids a peer puts in a request body (`StoreRequest.holder`,
16//!   `FindNodeRequest.requester`, …).
17//!
18//! Conflating them is the routing-table-poisoning bug class caught in PR #643:
19//! inserting `req.requester` into the routing table lets an authenticated peer
20//! seed arbitrary (unreachable) ids it does not own. With this type the handler
21//! signatures that update trusted state — [`super::routing::RoutingTable`]
22//! insertion via `note_peer_seen`, and the `holder == authenticated id` checks
23//! — take an `AuthenticatedNodeId`, so passing a wire value there is a *compile*
24//! error. Extracting the inner id requires an explicit
25//! [`AuthenticatedNodeId::node_id`] call,
26//! which documents exactly where trust is being asserted.
27//!
28//! There is intentionally **no** `From<NodeId>` / `From<[u8; 32]>` impl, and the
29//! only constructor takes a live [`Connection`] (not a bare `PublicKey`, which a
30//! caller could mint from arbitrary bytes): the type cannot be fabricated from
31//! wire data.
32//!
33//! # Scope
34//!
35//! This guards the *handler's direct trust decisions* — the per-request routing
36//! refresh (`note_peer_seen`) and the `holder` checks — against substituting a
37//! request-body id for the connected peer. It does **not** mean every routing
38//! insert requires authentication: the iterative-lookup / bucket-refresh /
39//! bootstrap paths legitimately learn peers from `FindNode` *responses* and
40//! insert those plain [`NodeId`]s as probe candidates (Kademlia by design,
41//! bounded by `MAX_CLOSER_NODES`-capped `closer_nodes` and the downstream
42//! active-staker filter). The boundary closes the requester/holder-substitution
43//! class specifically.
44
45use iroh::endpoint::Connection;
46
47use super::routing::NodeId;
48
49/// A [`NodeId`] proven by the QUIC handshake to be the connected peer's
50/// identity. Constructible only from an authenticated [`Connection`] — see the
51/// module docs for the trust rationale.
52#[derive(Copy, Clone, PartialEq, Eq, Debug)]
53pub struct AuthenticatedNodeId(NodeId);
54
55impl AuthenticatedNodeId {
56    /// Lift the authenticated identity of a live QUIC connection into the trust
57    /// boundary. Reads `conn.remote_id()` — the key iroh's handshake bound to
58    /// the connection — so there is no way to inject a peer-chosen id (a
59    /// `Connection` cannot be constructed with an arbitrary `remote_id`; only
60    /// the handshake sets it).
61    #[must_use]
62    pub fn from_connection(conn: &Connection) -> Self {
63        Self(NodeId::from_bytes(*conn.remote_id().as_bytes()))
64    }
65
66    /// The underlying [`NodeId`]. Calling this is the explicit, greppable
67    /// point at which an authenticated identity is compared against, or
68    /// inserted alongside, wire-provided ids.
69    #[must_use]
70    pub const fn node_id(&self) -> NodeId {
71        self.0
72    }
73}