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