Skip to main content

dig_stun/credential/
signed_client.rs

1//! The client state machine (`SPEC.md` §14.9): one signed Binding transaction, sending at most
2//! three datagrams (identity, signed, re-signed after one stale nonce) and respecting ONE overall
3//! timeout across all of them.
4
5use std::net::SocketAddr;
6use std::time::Duration;
7
8use tokio::net::UdpSocket;
9
10use crate::codec::{parse_binding_response, StunError, BINDING_SUCCESS};
11use crate::credential::request::encode_identity_request;
12use crate::credential::response::parse_challenge;
13use crate::credential::signature::StunSigner;
14use crate::credential::wire::{
15    is_valid_spki_der, BINDING_ERROR, ERR_STALE_NONCE, ERR_UNAUTHENTICATED, REALM,
16};
17use crate::scope::{scope_of, Scope};
18use crate::transaction_id::new_transaction_id;
19
20use super::request::encode_signed_request;
21
22/// Why a signed query did not produce a reflexive address (`SPEC.md` §14.9). Exhaustive: this
23/// crate defines no `StunError` variant for the credential (`SPEC.md` §14, "deliberately
24/// deferred"), so every credential-specific failure lives in this type instead.
25#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
26pub enum SignedQueryError {
27    /// An ordinary STUN-level failure — timeout, I/O, a malformed success, or a response of a
28    /// type this function does not expect at all.
29    #[error("{0}")]
30    Stun(#[from] StunError),
31    /// The server explicitly declined to answer with this code — a bare `401` from a `Required`
32    /// server, a repeated or foreign-realm `401`, a second `438`, or any other error code this
33    /// exchange does not know how to satisfy.
34    #[error("server refused with code {code}")]
35    Refused {
36        /// The RFC 5389 `ERROR-CODE` number the server sent.
37        code: u16,
38    },
39    /// The credential exchange cannot proceed: the signer's own SPKI is not a valid `SPEC.md`
40    /// §14.3.1 shape, so no request would ever be accepted.
41    #[error("credential exchange cannot proceed")]
42    BadChallenge,
43}
44
45/// The three datagrams this exchange may send, in order (`SPEC.md` §14.9): identity, then a signed
46/// request in response to the first challenge, then at most one re-signed request in response to a
47/// single stale-nonce reply. No stage ever sends a fourth datagram.
48enum Stage {
49    /// Just sent the identity request; awaiting the server's first reply.
50    Initial,
51    /// Just sent a signed request in response to a `401` challenge.
52    AfterFirstChallenge,
53    /// Just sent a re-signed request in response to a `438` stale-nonce reply. This is the last
54    /// datagram this exchange will ever send — any further challenge here is a refusal.
55    AfterReSign,
56}
57
58/// Perform one signed Binding transaction against a DIG-operated server (`SPEC.md` §14.9): the
59/// `operator:`/`relay:` tiers only — the `public:` tier MUST keep using
60/// [`crate::query_reflexive_address`], since a third-party server would ignore this attribute and
61/// the SPKI would otherwise disclose which DIG node is asking to a party the public tier does not
62/// tell today.
63///
64/// At most three datagrams are sent, all within the ONE `timeout` given here — the deadline is
65/// computed once and never reset by a resend. Any datagram not from `server`, or whose transaction
66/// id does not match the one currently outstanding, is discarded and the wait continues (a stale
67/// reply to an earlier step in this same exchange must not fail it).
68pub async fn query_reflexive_address_signed(
69    socket: &UdpSocket,
70    server: SocketAddr,
71    timeout: Duration,
72    signer: &dyn StunSigner,
73) -> Result<SocketAddr, SignedQueryError> {
74    let spki = signer.spki_der();
75    if !is_valid_spki_der(spki) {
76        return Err(SignedQueryError::BadChallenge);
77    }
78
79    let deadline = tokio::time::Instant::now() + timeout;
80    let mut buf = [0u8; 512];
81
82    let mut expected_txid = new_transaction_id();
83    send(
84        socket,
85        server,
86        &encode_identity_request(&expected_txid, spki),
87    )
88    .await?;
89    let mut stage = Stage::Initial;
90
91    loop {
92        let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
93        if remaining.is_zero() {
94            return Err(SignedQueryError::Stun(StunError::Timeout));
95        }
96        let (n, from) = match tokio::time::timeout(remaining, socket.recv_from(&mut buf)).await {
97            Ok(Ok(x)) => x,
98            Ok(Err(e)) => return Err(SignedQueryError::Stun(StunError::Io(e.to_string()))),
99            Err(_) => return Err(SignedQueryError::Stun(StunError::Timeout)),
100        };
101        if from != server {
102            continue; // not the server we asked — keep waiting for the genuine reply
103        }
104        let msg = &buf[..n];
105        if msg.len() < 2 {
106            return Err(SignedQueryError::Stun(StunError::Truncated));
107        }
108        let msg_type = u16::from_be_bytes([msg[0], msg[1]]);
109
110        if msg_type == BINDING_SUCCESS {
111            match parse_binding_response(msg, Some(&expected_txid)) {
112                Ok(addr) if scope_of(addr) == Scope::NeverDialable => {
113                    return Err(SignedQueryError::Stun(StunError::NoMappedAddress));
114                }
115                Ok(addr) => return Ok(addr),
116                Err(StunError::TransactionIdMismatch) => continue, // stale reply; keep waiting
117                Err(e) => return Err(SignedQueryError::Stun(e)),
118            }
119        } else if msg_type == BINDING_ERROR {
120            let challenge = match parse_challenge(msg, &expected_txid) {
121                Ok(c) => c,
122                Err(StunError::TransactionIdMismatch) => continue, // stale reply; keep waiting
123                Err(e) => return Err(SignedQueryError::Stun(e)),
124            };
125
126            match stage {
127                Stage::Initial => {
128                    let has_dig_realm = challenge.realm.as_deref() == Some(REALM);
129                    match (challenge.code, has_dig_realm, &challenge.nonce) {
130                        (ERR_UNAUTHENTICATED, true, Some(nonce)) => {
131                            expected_txid = new_transaction_id();
132                            let req = encode_signed_request(&expected_txid, nonce, signer);
133                            send(socket, server, &req).await?;
134                            stage = Stage::AfterFirstChallenge;
135                        }
136                        _ => {
137                            return Err(SignedQueryError::Refused {
138                                code: challenge.code,
139                            })
140                        }
141                    }
142                }
143                Stage::AfterFirstChallenge => match (challenge.code, &challenge.nonce) {
144                    (ERR_STALE_NONCE, Some(nonce)) => {
145                        expected_txid = new_transaction_id();
146                        let req = encode_signed_request(&expected_txid, nonce, signer);
147                        send(socket, server, &req).await?;
148                        stage = Stage::AfterReSign;
149                    }
150                    _ => {
151                        return Err(SignedQueryError::Refused {
152                            code: challenge.code,
153                        })
154                    }
155                },
156                Stage::AfterReSign => {
157                    // No fourth datagram is ever sent (`SPEC.md` §14.9): a second stale-nonce
158                    // reply, or anything else, ends the exchange as a refusal.
159                    return Err(SignedQueryError::Refused {
160                        code: challenge.code,
161                    });
162                }
163            }
164        } else {
165            return Err(SignedQueryError::Stun(StunError::UnexpectedType(msg_type)));
166        }
167    }
168}
169
170async fn send(
171    socket: &UdpSocket,
172    server: SocketAddr,
173    datagram: &[u8],
174) -> Result<(), SignedQueryError> {
175    socket
176        .send_to(datagram, server)
177        .await
178        .map(|_| ())
179        .map_err(|e| SignedQueryError::Stun(StunError::Io(e.to_string())))
180}