Skip to main content

tor_hsservice/
caps.rs

1//! Helpers for capability declaration and negotiation.
2
3// TODO #2598: Remove duplicate code between tor_hsclient::caps,
4// tor_hsservice::caps, and tor_proto::circuit (to the extent possible).
5
6use cfg_if::cfg_if;
7use tor_cell::relaycell::hs::intro_payload::IntroduceHandshakePayload;
8use tor_protover::Protocols;
9
10use crate::IntroRequestError;
11
12cfg_if! {
13    if #[cfg(feature = "negotiate-extensions")] {
14        use std::sync::LazyLock;
15        use tor_protover::{NamedSubver, named::*};
16
17        /// A set of protocols that we can advertise in our HS descriptor,
18        /// if we support them.
19        static ADVERTIZABLE_PROTOCOLS: LazyLock<Protocols> = LazyLock::new(
20           || [RELAY_CRYPT_CGO, RELAY_NEGOTIATE_SUBPROTO].into_iter().collect()
21        );
22
23        /// An array of protocols that can be requested via a subprotocol
24        /// request extension.
25        ///
26        /// Note that we don't necessarily support all of these ourselves!
27        static REQUESTABLE_LIST: &[NamedSubver] = &[
28             RELAY_CRYPT_CGO
29        ];
30
31        /// A set of protocols that can be requested via a subprotocol request extension.
32        static REQUESTABLE: LazyLock<Protocols> = LazyLock::new(
33            || REQUESTABLE_LIST.iter().copied().collect()
34        );
35
36        /// A set of protocols that we should advertise, if supported,
37        /// in our `flow-control` line.
38        static ADVERTISE_FLOWCTRL: LazyLock<Protocols> = LazyLock::new(
39            || [FLOWCTRL_AUTH_SENDME, FLOWCTRL_CC].into_iter().collect()
40        );
41    }
42}
43
44/// Return the list of protocols that we should advertise in our hsdesc.
45pub(crate) fn declared_protocols() -> Protocols {
46    cfg_if! {
47        if #[cfg(feature = "negotiate-extensions")] {
48            tor_proto::supported_client_protocols().intersection(&ADVERTIZABLE_PROTOCOLS)
49        } else {
50            Protocols::new()
51        }
52    }
53}
54
55/// Return the flow control information that we should include in our hsdesc.
56pub(crate) fn declared_flowctrl(
57    params: &tor_netdir::params::NetParameters,
58) -> Option<(Protocols, u8)> {
59    cfg_if! {
60        if #[cfg(feature = "negotiate-extensions")] {
61            let supported = tor_proto::supported_client_protocols();
62            supported.supports_named_subver(FLOWCTRL_CC).then(|| {
63                (
64                    supported.intersection(&ADVERTISE_FLOWCTRL),
65                    params.cc_sendme_inc.into()
66                )
67            })
68        } else {
69            None
70        }
71    }
72}
73
74/// Return a set of capabilities requested by the client in an INTRODUCE2 message payload.
75///
76/// The returned set will include capabilities that are...
77///    - Requested explicitly by using `SUBPROTOCOL_REQUEST`
78///    - Requested implicitly by using `FLOWCTRL_CC`
79///
80/// Returns an error if the client requested any capability that we do not support,
81/// or which may not be included in a list of requested protocols.
82#[cfg(feature = "negotiate-extensions")]
83pub(crate) fn negotiated_capabilities(
84    intro: &IntroduceHandshakePayload,
85) -> Result<Protocols, IntroRequestError> {
86    let mut requested = Vec::new();
87
88    // A list of capabilities (at the tor_proto level!) that we actually support.
89    //
90    // (We could possibly stop checking this once the flowctrl-cc and counter-galois-onion features
91    // are always-on.)
92    //
93    // We use this instead of annotating NEGOTIABLE_REQUEST_LIST members with `cfg`,
94    // because we don't want to add our own set of flowctrl-cc/counter-galois-onion features
95    // to match tor-proto.
96    let supported_protocols = tor_proto::supported_client_protocols();
97
98    if intro.cc_request_extension().is_some() {
99        // The client asked for FLOWCTRL_CC.  If we support it, add it to `requested`.
100        // Otherwise, reject the request.
101        if supported_protocols.supports_named_subver(FLOWCTRL_CC) {
102            requested.push(FLOWCTRL_CC);
103        } else {
104            return Err(IntroRequestError::UnsupportedCapability);
105        }
106    }
107
108    if let Some(sp) = intro.subprotocol_request_extension() {
109        if !sp.contains_only(&REQUESTABLE) {
110            // Some capability was listed that is not supported at all with this extension.
111            // Reject.
112            return Err(IntroRequestError::UnsupportedCapability);
113        }
114        for cap in REQUESTABLE_LIST {
115            if sp.contains(*cap) {
116                if !supported_protocols.supports_named_subver(*cap) {
117                    // They requested something that we don't support. Reject the request.
118                    return Err(IntroRequestError::UnsupportedCapability);
119                }
120                requested.push(*cap);
121            }
122        }
123    }
124
125    let requested: Protocols = requested.into_iter().collect();
126
127    // We cannot provide CGO unless we also have negotiated CC .
128    if requested.supports_named_subver(RELAY_CRYPT_CGO)
129        && !requested.supports_named_subver(FLOWCTRL_CC)
130    {
131        return Err(IntroRequestError::UnsupportedCapability);
132    }
133
134    Ok(requested)
135}
136
137/// Legacy implementation of negotiated_capabilities.
138///
139// TODO: Remove this, and make the entire negotiate-extensions feature always-on.
140#[cfg(not(feature = "negotiate-extensions"))]
141pub(crate) fn negotiated_capabilities(
142    intro: &IntroduceHandshakePayload,
143) -> Result<Protocols, IntroRequestError> {
144    // In this case, we "do not recognize" SUBPROTO_REQUEST,
145    // and so we ignore it.
146
147    Ok(Protocols::new())
148}