Skip to main content

tor_cell/relaycell/hs/
intro_payload.rs

1//! The encrypted portion of an INTRODUCE{1,2} message.
2//!
3//! (This is as described as the "decrypted plaintext" in section 3.3 of
4//! rend-spec-v3.txt.  It tells the onion service how to find the rendezvous
5//! point, and how to handshake with the client there.)
6
7use super::pow::ProofOfWork;
8use crate::relaycell::{
9    extend::{CcRequest, CircRequestExt, SubprotocolRequest},
10    extlist::ExtList,
11};
12use caret::caret_int;
13use tor_bytes::{EncodeError, EncodeResult, Error, Readable, Reader, Result, Writeable, Writer};
14use tor_hscrypto::RendCookie;
15use tor_linkspec::EncodedLinkSpec;
16
17caret_int! {
18    /// An enumeration value to identify a type of onion key.
19    ///
20    /// Corresponds to `ONION_KEY_TYPE` in section 3.3 of
21    /// rend-spec-v3.txt \[PROCESS_INTRO].
22    //
23    // TODO this shouldn't live here.  It ought to be in some more general crate.
24    // But it should then also be usable in the netdoc parser.  In particular, it ought
25    // to be able to handle the *textual* values in `hsdesc/inner.rs`, and maybe
26    // the ad-hocery in the routerdesc parsing too.
27    struct OnionKeyType(u8) {
28        NTOR = 0x01,
29    }
30}
31
32/// An onion key provided in an IntroduceHandshakePayload.
33///
34/// Corresponds to `ONION_KEY` in the spec.
35//
36// TODO: Is there a logical type somewhere else to coalesce this with?
37// Currently there is no wrapper around curve25519::PublicKey when it's used as
38// an Ntor key, nor is there (yet) a generic onion key enum.  tor-linkspec might be
39// the logical place for those.  See arti#893.
40#[derive(Clone, Debug)]
41#[non_exhaustive]
42pub enum OnionKey {
43    /// A key usable with the ntor or ntor-v3 handshake.
44    NtorOnionKey(tor_llcrypto::pk::curve25519::PublicKey),
45    // There is no "unknown" variant for this type, since we don't support any
46    // other key types yet.
47}
48
49impl Readable for OnionKey {
50    fn take_from(r: &mut Reader<'_>) -> Result<Self> {
51        let kind: OnionKeyType = r.take_u8()?.into();
52        r.read_nested_u16len(|r_inner| match kind {
53            OnionKeyType::NTOR => Ok(OnionKey::NtorOnionKey(r_inner.extract()?)),
54            _ => Err(Error::InvalidMessage(
55                format!("Unrecognized onion key type {kind}").into(),
56            )),
57        })
58    }
59}
60
61impl Writeable for OnionKey {
62    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
63        match self {
64            OnionKey::NtorOnionKey(key) => {
65                w.write_u8(OnionKeyType::NTOR.into());
66                let mut w_inner = w.write_nested_u16len();
67                w_inner.write(key)?;
68                w_inner.finish()?;
69            }
70        }
71        Ok(())
72    }
73}
74
75/// The plaintext of the encrypted portion of an INTRODUCE{1,2} message.
76///
77/// This is not a RelayMsg itself; it is instead used as the payload for an
78/// `hs-ntor` handshake, which is passed to the onion service in `Introduce[12]`
79/// message.
80///
81/// This payload is sent from a client to the onion service to tell it how to reach
82/// the client's chosen rendezvous point.
83///
84/// This corresponds to the "decrypted payload" in section 3.3 of
85/// rend-spec-v3.txt, **excluding the PAD field**.
86///
87/// The user of this type is expected to discard, or generate, appropriate
88/// padding, as required.
89#[derive(Clone, Debug)]
90pub struct IntroduceHandshakePayload {
91    /// The rendezvous cookie to use at the rendezvous point.
92    ///
93    /// (`RENDEZVOUS_COOKIE` in the spec.)
94    cookie: RendCookie,
95    /// A list of extensions to this payload.
96    ///
97    /// (`N_EXTENSIONS`, `EXT_FIELD_TYPE`, `EXT_FIELD_LEN`, and `EXT_FIELD` in
98    /// the spec.)
99    extensions: ExtList<CircRequestExt>,
100    /// The onion key to use when extending a circuit to the rendezvous point.
101    ///
102    /// (`ONION_KEY_TYPE`, `ONION_KEY_LEN`, and `ONION_KEY` in the spec. This
103    /// represents `KP_ntor` for the rendezvous point.)
104    onion_key: OnionKey,
105    /// A list of link specifiers to identify the rendezvous point.
106    ///
107    /// (`NSPEC`, `LSTYPE`, `LSLEN`, and `LSPEC` in the spec.)
108    link_specifiers: Vec<EncodedLinkSpec>,
109}
110
111impl Readable for IntroduceHandshakePayload {
112    fn take_from(r: &mut Reader<'_>) -> Result<Self> {
113        let cookie = r.extract()?;
114        let extensions = r.extract()?;
115        let onion_key = r.extract()?;
116        let n_link_specifiers = r.take_u8()?;
117        let link_specifiers = r.extract_n(n_link_specifiers.into())?;
118        Ok(Self {
119            cookie,
120            extensions,
121            onion_key,
122            link_specifiers,
123        })
124    }
125}
126
127impl Writeable for IntroduceHandshakePayload {
128    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
129        w.write(&self.cookie)?;
130        w.write(&self.extensions)?;
131        w.write(&self.onion_key)?;
132        w.write_u8(
133            self.link_specifiers
134                .len()
135                .try_into()
136                .map_err(|_| EncodeError::BadLengthValue)?,
137        );
138        self.link_specifiers.iter().try_for_each(|ls| w.write(ls))?;
139
140        Ok(())
141    }
142}
143
144impl IntroduceHandshakePayload {
145    /// Construct a new [`IntroduceHandshakePayload`]
146    pub fn new(
147        cookie: RendCookie,
148        onion_key: OnionKey,
149        link_specifiers: Vec<EncodedLinkSpec>,
150        proof_of_work: Option<ProofOfWork>,
151    ) -> Self {
152        let mut extensions = ExtList::default();
153        if let Some(proof_of_work) = proof_of_work {
154            extensions.push(proof_of_work.into());
155        }
156        Self {
157            cookie,
158            extensions,
159            onion_key,
160            link_specifiers,
161        }
162    }
163
164    /// Return the rendezvous cookie specified in this handshake payload.
165    pub fn cookie(&self) -> &RendCookie {
166        &self.cookie
167    }
168
169    /// Return the provided onion key for the specified rendezvous point
170    pub fn onion_key(&self) -> &OnionKey {
171        &self.onion_key
172    }
173
174    /// Return the provided link specifiers for the specified rendezvous point.
175    pub fn link_specifiers(&self) -> &[EncodedLinkSpec] {
176        &self.link_specifiers[..]
177    }
178
179    /// Return the proof-of-work extension for this request.
180    pub fn proof_of_work_extension(&self) -> Option<&ProofOfWork> {
181        self.extensions.get_proof_of_work()
182    }
183
184    /// Return the cc request extension for this request.
185    pub fn cc_request_extension(&self) -> Option<&CcRequest> {
186        self.extensions.get_cc_request()
187    }
188
189    /// Return the subprotocol request extension for this request.
190    pub fn subprotocol_request_extension(&self) -> Option<&SubprotocolRequest> {
191        self.extensions.get_subprotocol_request()
192    }
193
194    /// Add a new extension `ext` to the list of extensions in this payload.
195    pub fn add_extension(&mut self, ext: CircRequestExt) {
196        self.extensions.push(ext);
197    }
198}