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

            
7
use super::ext::{decl_extension_group, Ext, ExtGroup, ExtList, UnrecognizedExt};
8
use super::pow::ProofOfWork;
9
use caret::caret_int;
10
use tor_bytes::{EncodeError, EncodeResult, Error, Readable, Reader, Result, Writeable, Writer};
11
use tor_hscrypto::RendCookie;
12
use tor_linkspec::EncodedLinkSpec;
13

            
14
caret_int! {
15
    /// Type code for an extension in an [`IntroduceHandshakePayload`].
16
    #[derive(Ord,PartialOrd)]
17
    pub struct IntroPayloadExtType(u8) {
18
        /// The extension to provide a completed proof-of-work solution for denial of service
19
        /// mitigation
20
        PROOF_OF_WORK = 2,
21
    }
22
}
23

            
24
decl_extension_group! {
25
    /// An extension to an [`IntroduceHandshakePayload`].
26
    #[derive(Debug,Clone)]
27
    enum IntroPayloadExt [ IntroPayloadExtType ] {
28
        ProofOfWork,
29
    }
30
}
31

            
32
caret_int! {
33
    /// An enumeration value to identify a type of onion key.
34
    ///
35
    /// Corresponds to `ONION_KEY_TYPE` in section 3.3 of
36
    /// rend-spec-v3.txt \[PROCESS_INTRO].
37
    //
38
    // TODO this shouldn't live here.  It ought to be in some more general crate.
39
    // But it should then also be usable in the netdoc parser.  In particular, it ought
40
    // to be able to handle the *textual* values in `hsdesc/inner.rs`, and maybe
41
    // the ad-hocery in the routerdesc parsing too.
42
    struct OnionKeyType(u8) {
43
        NTOR = 0x01,
44
    }
45
}
46

            
47
/// An onion key provided in an IntroduceHandshakePayload.
48
///
49
/// Corresponds to `ONION_KEY` in the spec.
50
//
51
// TODO: Is there a logical type somewhere else to coalesce this with?
52
// Currently there is no wrapper around curve25519::PublicKey when it's used as
53
// an Ntor key, nor is there (yet) a generic onion key enum.  tor-linkspec might be
54
// the logical place for those.  See arti#893.
55
#[derive(Clone, Debug)]
56
#[non_exhaustive]
57
pub enum OnionKey {
58
    /// A key usable with the ntor or ntor-v3 handshake.
59
    NtorOnionKey(tor_llcrypto::pk::curve25519::PublicKey),
60
    // There is no "unknown" variant for this type, since we don't support any
61
    // other key types yet.
62
}
63

            
64
impl Readable for OnionKey {
65
49
    fn take_from(r: &mut Reader<'_>) -> Result<Self> {
66
49
        let kind: OnionKeyType = r.take_u8()?.into();
67
50
        r.read_nested_u16len(|r_inner| match kind {
68
49
            OnionKeyType::NTOR => Ok(OnionKey::NtorOnionKey(r_inner.extract()?)),
69
            _ => Err(Error::InvalidMessage(
70
                format!("Unrecognized onion key type {kind}").into(),
71
            )),
72
50
        })
73
49
    }
74
}
75

            
76
impl Writeable for OnionKey {
77
2
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
78
2
        match self {
79
2
            OnionKey::NtorOnionKey(key) => {
80
2
                w.write_u8(OnionKeyType::NTOR.into());
81
2
                let mut w_inner = w.write_nested_u16len();
82
2
                w_inner.write(key)?;
83
2
                w_inner.finish()?;
84
            }
85
        }
86
2
        Ok(())
87
2
    }
88
}
89

            
90
/// The plaintext of the encrypted portion of an INTRODUCE{1,2} message.
91
///
92
/// This is not a RelayMsg itself; it is instead used as the payload for an
93
/// `hs-ntor` handshake, which is passed to the onion service in `Introduce[12]`
94
/// message.
95
///
96
/// This payload is sent from a client to the onion service to tell it how to reach
97
/// the client's chosen rendezvous point.
98
///
99
/// This corresponds to the "decrypted payload" in section 3.3 of
100
/// rend-spec-v3.txt, **excluding the PAD field**.
101
///
102
/// The user of this type is expected to discard, or generate, appropriate
103
/// padding, as required.
104
#[derive(Clone, Debug)]
105
pub struct IntroduceHandshakePayload {
106
    /// The rendezvous cookie to use at the rendezvous point.
107
    ///
108
    /// (`RENDEZVOUS_COOKIE` in the spec.)
109
    cookie: RendCookie,
110
    /// A list of extensions to this payload.
111
    ///
112
    /// (`N_EXTENSIONS`, `EXT_FIELD_TYPE`, `EXT_FIELD_LEN`, and `EXT_FIELD` in
113
    /// the spec.)
114
    extensions: ExtList<IntroPayloadExt>,
115
    /// The onion key to use when extending a circuit to the rendezvous point.
116
    ///
117
    /// (`ONION_KEY_TYPE`, `ONION_KEY_LEN`, and `ONION_KEY` in the spec. This
118
    /// represents `KP_ntor` for the rendezvous point.)
119
    onion_key: OnionKey,
120
    /// A list of link specifiers to identify the rendezvous point.
121
    ///
122
    /// (`NSPEC`, `LSTYPE`, `LSLEN`, and `LSPEC` in the spec.)
123
    link_specifiers: Vec<EncodedLinkSpec>,
124
}
125

            
126
impl Readable for IntroduceHandshakePayload {
127
49
    fn take_from(r: &mut Reader<'_>) -> Result<Self> {
128
49
        let cookie = r.extract()?;
129
49
        let extensions = r.extract()?;
130
49
        let onion_key = r.extract()?;
131
49
        let n_link_specifiers = r.take_u8()?;
132
49
        let link_specifiers = r.extract_n(n_link_specifiers.into())?;
133
49
        Ok(Self {
134
49
            cookie,
135
49
            extensions,
136
49
            onion_key,
137
49
            link_specifiers,
138
49
        })
139
49
    }
140
}
141

            
142
impl Writeable for IntroduceHandshakePayload {
143
2
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
144
2
        w.write(&self.cookie)?;
145
2
        w.write(&self.extensions)?;
146
2
        w.write(&self.onion_key)?;
147
2
        w.write_u8(
148
2
            self.link_specifiers
149
2
                .len()
150
2
                .try_into()
151
2
                .map_err(|_| EncodeError::BadLengthValue)?,
152
        );
153
6
        self.link_specifiers.iter().try_for_each(|ls| w.write(ls))?;
154

            
155
2
        Ok(())
156
2
    }
157
}
158

            
159
impl IntroduceHandshakePayload {
160
    /// Construct a new [`IntroduceHandshakePayload`]
161
49
    pub fn new(
162
49
        cookie: RendCookie,
163
49
        onion_key: OnionKey,
164
49
        link_specifiers: Vec<EncodedLinkSpec>,
165
49
        proof_of_work: Option<ProofOfWork>,
166
49
    ) -> Self {
167
49
        let mut extensions = ExtList::default();
168
49
        if let Some(proof_of_work) = proof_of_work {
169
            extensions.push(proof_of_work.into());
170
49
        }
171
49
        Self {
172
49
            cookie,
173
49
            extensions,
174
49
            onion_key,
175
49
            link_specifiers,
176
49
        }
177
49
    }
178

            
179
    /// Return the rendezvous cookie specified in this handshake payload.
180
    pub fn cookie(&self) -> &RendCookie {
181
        &self.cookie
182
    }
183

            
184
    /// Return the provided onion key for the specified rendezvous point
185
    pub fn onion_key(&self) -> &OnionKey {
186
        &self.onion_key
187
    }
188

            
189
    /// Return the provided link specifiers for the specified rendezvous point.
190
    pub fn link_specifiers(&self) -> &[EncodedLinkSpec] {
191
        &self.link_specifiers[..]
192
    }
193
}