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::pow::ProofOfWork;
8
use crate::relaycell::{extend::CircRequestExt, extlist::ExtList};
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
    /// An enumeration value to identify a type of onion key.
16
    ///
17
    /// Corresponds to `ONION_KEY_TYPE` in section 3.3 of
18
    /// rend-spec-v3.txt \[PROCESS_INTRO].
19
    //
20
    // TODO this shouldn't live here.  It ought to be in some more general crate.
21
    // But it should then also be usable in the netdoc parser.  In particular, it ought
22
    // to be able to handle the *textual* values in `hsdesc/inner.rs`, and maybe
23
    // the ad-hocery in the routerdesc parsing too.
24
    struct OnionKeyType(u8) {
25
        NTOR = 0x01,
26
    }
27
}
28

            
29
/// An onion key provided in an IntroduceHandshakePayload.
30
///
31
/// Corresponds to `ONION_KEY` in the spec.
32
//
33
// TODO: Is there a logical type somewhere else to coalesce this with?
34
// Currently there is no wrapper around curve25519::PublicKey when it's used as
35
// an Ntor key, nor is there (yet) a generic onion key enum.  tor-linkspec might be
36
// the logical place for those.  See arti#893.
37
#[derive(Clone, Debug)]
38
#[non_exhaustive]
39
pub enum OnionKey {
40
    /// A key usable with the ntor or ntor-v3 handshake.
41
    NtorOnionKey(tor_llcrypto::pk::curve25519::PublicKey),
42
    // There is no "unknown" variant for this type, since we don't support any
43
    // other key types yet.
44
}
45

            
46
impl Readable for OnionKey {
47
55
    fn take_from(r: &mut Reader<'_>) -> Result<Self> {
48
55
        let kind: OnionKeyType = r.take_u8()?.into();
49
56
        r.read_nested_u16len(|r_inner| match kind {
50
55
            OnionKeyType::NTOR => Ok(OnionKey::NtorOnionKey(r_inner.extract()?)),
51
            _ => Err(Error::InvalidMessage(
52
                format!("Unrecognized onion key type {kind}").into(),
53
            )),
54
56
        })
55
55
    }
56
}
57

            
58
impl Writeable for OnionKey {
59
2
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
60
2
        match self {
61
2
            OnionKey::NtorOnionKey(key) => {
62
2
                w.write_u8(OnionKeyType::NTOR.into());
63
2
                let mut w_inner = w.write_nested_u16len();
64
2
                w_inner.write(key)?;
65
2
                w_inner.finish()?;
66
            }
67
        }
68
2
        Ok(())
69
2
    }
70
}
71

            
72
/// The plaintext of the encrypted portion of an INTRODUCE{1,2} message.
73
///
74
/// This is not a RelayMsg itself; it is instead used as the payload for an
75
/// `hs-ntor` handshake, which is passed to the onion service in `Introduce[12]`
76
/// message.
77
///
78
/// This payload is sent from a client to the onion service to tell it how to reach
79
/// the client's chosen rendezvous point.
80
///
81
/// This corresponds to the "decrypted payload" in section 3.3 of
82
/// rend-spec-v3.txt, **excluding the PAD field**.
83
///
84
/// The user of this type is expected to discard, or generate, appropriate
85
/// padding, as required.
86
#[derive(Clone, Debug)]
87
pub struct IntroduceHandshakePayload {
88
    /// The rendezvous cookie to use at the rendezvous point.
89
    ///
90
    /// (`RENDEZVOUS_COOKIE` in the spec.)
91
    cookie: RendCookie,
92
    /// A list of extensions to this payload.
93
    ///
94
    /// (`N_EXTENSIONS`, `EXT_FIELD_TYPE`, `EXT_FIELD_LEN`, and `EXT_FIELD` in
95
    /// the spec.)
96
    extensions: ExtList<CircRequestExt>,
97
    /// The onion key to use when extending a circuit to the rendezvous point.
98
    ///
99
    /// (`ONION_KEY_TYPE`, `ONION_KEY_LEN`, and `ONION_KEY` in the spec. This
100
    /// represents `KP_ntor` for the rendezvous point.)
101
    onion_key: OnionKey,
102
    /// A list of link specifiers to identify the rendezvous point.
103
    ///
104
    /// (`NSPEC`, `LSTYPE`, `LSLEN`, and `LSPEC` in the spec.)
105
    link_specifiers: Vec<EncodedLinkSpec>,
106
}
107

            
108
impl Readable for IntroduceHandshakePayload {
109
55
    fn take_from(r: &mut Reader<'_>) -> Result<Self> {
110
55
        let cookie = r.extract()?;
111
55
        let extensions = r.extract()?;
112
55
        let onion_key = r.extract()?;
113
55
        let n_link_specifiers = r.take_u8()?;
114
55
        let link_specifiers = r.extract_n(n_link_specifiers.into())?;
115
55
        Ok(Self {
116
55
            cookie,
117
55
            extensions,
118
55
            onion_key,
119
55
            link_specifiers,
120
55
        })
121
55
    }
122
}
123

            
124
impl Writeable for IntroduceHandshakePayload {
125
2
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
126
2
        w.write(&self.cookie)?;
127
2
        w.write(&self.extensions)?;
128
2
        w.write(&self.onion_key)?;
129
2
        w.write_u8(
130
2
            self.link_specifiers
131
2
                .len()
132
2
                .try_into()
133
2
                .map_err(|_| EncodeError::BadLengthValue)?,
134
        );
135
6
        self.link_specifiers.iter().try_for_each(|ls| w.write(ls))?;
136

            
137
2
        Ok(())
138
2
    }
139
}
140

            
141
impl IntroduceHandshakePayload {
142
    /// Construct a new [`IntroduceHandshakePayload`]
143
55
    pub fn new(
144
55
        cookie: RendCookie,
145
55
        onion_key: OnionKey,
146
55
        link_specifiers: Vec<EncodedLinkSpec>,
147
55
        proof_of_work: Option<ProofOfWork>,
148
55
    ) -> Self {
149
55
        let mut extensions = ExtList::default();
150
55
        if let Some(proof_of_work) = proof_of_work {
151
            extensions.push(proof_of_work.into());
152
55
        }
153
55
        Self {
154
55
            cookie,
155
55
            extensions,
156
55
            onion_key,
157
55
            link_specifiers,
158
55
        }
159
55
    }
160

            
161
    /// Return the rendezvous cookie specified in this handshake payload.
162
    pub fn cookie(&self) -> &RendCookie {
163
        &self.cookie
164
    }
165

            
166
    /// Return the provided onion key for the specified rendezvous point
167
    pub fn onion_key(&self) -> &OnionKey {
168
        &self.onion_key
169
    }
170

            
171
    /// Return the provided link specifiers for the specified rendezvous point.
172
    pub fn link_specifiers(&self) -> &[EncodedLinkSpec] {
173
        &self.link_specifiers[..]
174
    }
175

            
176
    /// Return the proof-of-work extension for the specified rendezvous point.
177
    pub fn proof_of_work_extension(&self) -> Option<&ProofOfWork> {
178
        // TODO: it would be nice to change ExtList to provide a nicer API for this...
179
        self.extensions.extensions.iter().find_map(|x| match x {
180
            CircRequestExt::ProofOfWork(x) => Some(x),
181
            _ => None,
182
        })
183
    }
184
}