1use log::info;
2
3use crate::crypto::{
4 ct_equal, hkdf_sha256_expand, hmac_sha256, kdf_tor, random_bytes, x25519_public_from_private,
5 x25519_shared, DigestKind, RelayCrypto,
6};
7use crate::directory::{decode_http_body, same_relay};
8use crate::directory::{relay_link_specifiers, serialize_link_specifiers, Relay};
9use crate::error::{ensure, err, Result};
10use crate::tor::{
11 TorChannel, CMD_CREATED_FAST, CMD_CREATE_FAST, CMD_DESTROY, CMD_RELAY, CMD_RELAY_EARLY,
12 K_CELL_BODY_LEN, K_RELAY_HEADER_LEN, K_RELAY_PAYLOAD_LEN, RELAY_BEGIN_DIR, RELAY_CONNECTED,
13 RELAY_DATA, RELAY_END, RELAY_EXTEND2, RELAY_EXTENDED2, RELAY_SENDME,
14};
15use crate::util::{from_string, put_u16, read_u16, Bytes};
16
17#[derive(Clone, Debug, Default)]
18pub struct NtorState {
19 pub x: Bytes,
20 pub x_pub: Bytes,
21 pub b: Bytes,
22 pub id: Bytes,
23 pub bx: Bytes,
24}
25
26pub fn build_ntor_onionskin(relay: &Relay, st: &mut NtorState) -> Result<Bytes> {
27 ensure(
28 relay.rsa_id.len() == 20,
29 "relay missing RSA identity for ntor",
30 )?;
31 ensure(relay.ntor_key.len() == 32, "relay missing ntor key")?;
32 st.x = random_bytes(32);
33 st.x_pub = x25519_public_from_private(&st.x)?;
34 st.b = relay.ntor_key.clone();
35 st.id = relay.rsa_id.clone();
36 st.bx = x25519_shared(&st.x, &st.b)?;
37 let mut hdata = st.id.clone();
38 hdata.extend_from_slice(&st.b);
39 hdata.extend_from_slice(&st.x_pub);
40 Ok(hdata)
41}
42
43pub fn finish_ntor(st: &NtorState, hdata: &[u8]) -> Result<RelayCrypto> {
44 ensure(hdata.len() >= 64, "short ntor CREATED2 data")?;
45 let y = &hdata[..32];
46 let auth = &hdata[32..64];
47 let yx = x25519_shared(&st.x, y)?;
48 let proto = from_string("ntor-curve25519-sha256-1");
49 let mut secret = yx;
50 secret.extend_from_slice(&st.bx);
51 secret.extend_from_slice(&st.id);
52 secret.extend_from_slice(&st.b);
53 secret.extend_from_slice(&st.x_pub);
54 secret.extend_from_slice(y);
55 secret.extend_from_slice(&proto);
56 let t_key = from_string("ntor-curve25519-sha256-1:key_extract");
57 let t_verify = from_string("ntor-curve25519-sha256-1:verify");
58 let t_mac = from_string("ntor-curve25519-sha256-1:mac");
59 let key_seed = hmac_sha256(&t_key, &secret)?;
60 let verify = hmac_sha256(&t_verify, &secret)?;
61 let mut auth_input = verify;
62 auth_input.extend_from_slice(&st.id);
63 auth_input.extend_from_slice(&st.b);
64 auth_input.extend_from_slice(y);
65 auth_input.extend_from_slice(&st.x_pub);
66 auth_input.extend_from_slice(&proto);
67 auth_input.extend_from_slice(b"Server");
68 let expected = hmac_sha256(&t_mac, &auth_input)?;
69 ensure(ct_equal(auth, &expected), "ntor auth mismatch")?;
70 let k = hkdf_sha256_expand(&key_seed, b"ntor-curve25519-sha256-1:key_expand", 92)?;
71 Ok(RelayCrypto::new(
72 &k[0..20],
73 &k[20..40],
74 &k[40..56],
75 &k[56..72],
76 DigestKind::Sha1,
77 ))
78}
79
80#[derive(Clone, Debug, Default)]
81pub struct RelayMessage {
82 pub cmd: u8,
83 pub stream_id: u16,
84 pub data: Bytes,
85}
86
87pub struct Circuit {
88 ch: TorChannel,
89 id: u32,
90 hops: Vec<RelayCrypto>,
91}
92
93impl Circuit {
94 pub fn create_fast(mut ch: TorChannel) -> Result<Self> {
95 let id = ch.new_circ_id();
96 info!("creating fast circuit {id}");
97 let x = random_bytes(20);
98 let mut body = x.clone();
99 body.resize(K_CELL_BODY_LEN, 0);
100 ch.write_cell(id, CMD_CREATE_FAST, &body)?;
101 loop {
102 let c = ch.read_cell()?;
103 if c.circ_id != id {
104 continue;
105 }
106 if c.cmd == CMD_DESTROY {
107 return err("CREATE_FAST was destroyed");
108 }
109 ensure(
110 c.cmd == CMD_CREATED_FAST,
111 "unexpected cell while waiting for CREATED_FAST",
112 )?;
113 let y = &c.body[..20];
114 let kh = &c.body[20..40];
115 let mut k0 = x;
116 k0.extend_from_slice(y);
117 let k = kdf_tor(&k0, 92);
118 ensure(ct_equal(kh, &k[..20]), "CREATE_FAST key hash mismatch")?;
119 let rc = RelayCrypto::new(
120 &k[20..40],
121 &k[40..60],
122 &k[60..76],
123 &k[76..92],
124 DigestKind::Sha1,
125 );
126 return Ok(Self {
127 ch,
128 id,
129 hops: vec![rc],
130 });
131 }
132 }
133
134 pub fn send_relay(&mut self, cmd: u8, stream_id: u16, data: &[u8]) -> Result<()> {
135 let hop_index = self.hops.len() - 1;
136 self.send_relay_cell(CMD_RELAY, hop_index, cmd, stream_id, data)
137 }
138
139 pub fn recv_relay(&mut self) -> Result<RelayMessage> {
140 loop {
141 let c = self.ch.read_cell()?;
142 if c.circ_id != self.id {
143 continue;
144 }
145 if c.cmd == CMD_DESTROY {
146 return err("circuit destroyed");
147 }
148 if c.cmd != CMD_RELAY {
149 continue;
150 }
151 let mut body = c.body;
152 for hop in &mut self.hops {
153 body = hop.decrypt_body_only(&body)?;
154 if hop.recognize_decrypted(&body) {
155 return parse_relay_body(&body);
156 }
157 }
158 }
159 }
160
161 pub fn send_raw_body(&mut self, body: &[u8]) -> Result<()> {
162 ensure(
163 body.len() == K_CELL_BODY_LEN,
164 "raw relay body must be one cell body",
165 )?;
166 let mut encrypted = body.to_vec();
167 for hop in self.hops.iter_mut().rev() {
168 encrypted = hop.encrypt_body_only(&encrypted)?;
169 }
170 self.ch.write_cell(self.id, CMD_RELAY, &encrypted)
171 }
172
173 pub fn recv_raw_body(&mut self) -> Result<Bytes> {
174 loop {
175 let c = self.ch.read_cell()?;
176 if c.circ_id != self.id {
177 continue;
178 }
179 if c.cmd == CMD_DESTROY {
180 return err("rendezvous circuit destroyed");
181 }
182 if c.cmd == CMD_RELAY {
183 let mut body = c.body;
184 for hop in &mut self.hops {
185 body = hop.decrypt_body_only(&body)?;
186 }
187 return Ok(body);
188 }
189 }
190 }
191
192 pub fn extend_ntor(&mut self, relay: &Relay) -> Result<()> {
193 info!(
194 "extending circuit to relay {} at {}:{}",
195 relay.nickname, relay.ip, relay.or_port
196 );
197 let mut st = NtorState::default();
198 let hdata = build_ntor_onionskin(relay, &mut st)?;
199 let lspec = serialize_link_specifiers(&relay_link_specifiers(relay)?)?;
200 let mut data = lspec;
201 put_u16(&mut data, 2);
202 put_u16(&mut data, hdata.len() as u16);
203 data.extend_from_slice(&hdata);
204 let hop_index = self.hops.len() - 1;
205 self.send_relay_cell(CMD_RELAY_EARLY, hop_index, RELAY_EXTEND2, 0, &data)?;
206 loop {
207 let m = self.recv_relay()?;
208 if m.cmd == RELAY_EXTENDED2 {
209 ensure(m.data.len() >= 2, "short EXTENDED2 body")?;
210 let hlen = read_u16(&m.data, 0)? as usize;
211 ensure(m.data.len() >= 2 + hlen, "truncated EXTENDED2 handshake")?;
212 let created = &m.data[2..2 + hlen];
213 self.hops.push(finish_ntor(&st, created)?);
214 return Ok(());
215 }
216 }
217 }
218
219 fn send_relay_cell(
220 &mut self,
221 cell_cmd: u8,
222 hop_index: usize,
223 relay_cmd: u8,
224 stream_id: u16,
225 data: &[u8],
226 ) -> Result<()> {
227 ensure(hop_index < self.hops.len(), "bad hop index")?;
228 let mut body = self.hops[hop_index].encrypt_relay(relay_cmd, stream_id, data)?;
229 for i in (0..hop_index).rev() {
230 body = self.hops[i].encrypt_body_only(&body)?;
231 }
232 self.ch.write_cell(self.id, cell_cmd, &body)
233 }
234}
235
236pub fn parse_relay_body(body: &[u8]) -> Result<RelayMessage> {
237 ensure(body.len() == K_CELL_BODY_LEN, "bad relay body")?;
238 let len = read_u16(body, 9)? as usize;
239 ensure(len <= K_RELAY_PAYLOAD_LEN, "relay length too large")?;
240 Ok(RelayMessage {
241 cmd: body[0],
242 stream_id: read_u16(body, 3)?,
243 data: body[K_RELAY_HEADER_LEN..K_RELAY_HEADER_LEN + len].to_vec(),
244 })
245}
246
247pub fn begin_dir_get_via(
248 guard: &Relay,
249 target: &Relay,
250 path: &str,
251 timeout_ms: i32,
252) -> Result<Bytes> {
253 info!(
254 "opening BEGIN_DIR request to {} via guard {}",
255 target.nickname, guard.nickname
256 );
257 let ch = TorChannel::new(guard.ip.clone(), guard.or_port, timeout_ms)?;
258 let mut circ = Circuit::create_fast(ch)?;
259 circ.extend_ntor(target)?;
260 circ.send_relay(RELAY_BEGIN_DIR, 1, &[])?;
261 loop {
262 let m = circ.recv_relay()?;
263 if m.cmd == RELAY_CONNECTED && m.stream_id == 1 {
264 break;
265 }
266 if m.cmd == RELAY_END && m.stream_id == 1 {
267 return err(format!("BEGIN_DIR rejected by {}", target.nickname));
268 }
269 }
270 let req = format!(
271 "GET {path} HTTP/1.0\r\nHost: {}\r\nUser-Agent: onionlink/0\r\nAccept-Encoding: identity\r\nConnection: close\r\n\r\n",
272 target.ip
273 );
274 let rb = from_string(req);
275 for chunk in rb.chunks(K_RELAY_PAYLOAD_LEN) {
276 circ.send_relay(RELAY_DATA, 1, chunk)?;
277 }
278 let mut response = Bytes::new();
279 let mut circ_window = 1000;
280 let mut stream_window = 500;
281 loop {
282 let m = circ.recv_relay()?;
283 if m.cmd == RELAY_DATA && m.stream_id == 1 {
284 response.extend_from_slice(&m.data);
285 circ_window -= 1;
286 if circ_window <= 900 {
287 circ.send_relay(RELAY_SENDME, 0, &[0, 0, 0])?;
288 circ_window += 100;
289 }
290 stream_window -= 1;
291 if stream_window <= 450 {
292 circ.send_relay(RELAY_SENDME, 1, &[])?;
293 stream_window += 50;
294 }
295 } else if m.cmd == RELAY_END && m.stream_id == 1 {
296 break;
297 }
298 if response.len() > 8 * 1024 * 1024 {
299 return err("BEGIN_DIR response too large");
300 }
301 }
302 decode_http_body(&response)
303}
304
305pub fn connect_guard_circuit(guard: &Relay, timeout_ms: i32) -> Result<Circuit> {
306 Circuit::create_fast(TorChannel::new(
307 guard.ip.clone(),
308 guard.or_port,
309 timeout_ms,
310 )?)
311}
312
313pub fn extend_unless_same(circ: &mut Circuit, guard: &Relay, target: &Relay) -> Result<()> {
314 if !same_relay(guard, target) {
315 circ.extend_ntor(target)?;
316 }
317 Ok(())
318}