1use std::clone::Clone;
3use std::convert::TryInto;
4use std::net::TcpStream;
5
6use bson::doc;
8use bson::spec::BinarySubtype;
9use bson::{Binary, Bson};
10use honk_rpc::honk_rpc::{
11 get_message_overhead, get_request_section_size, RequestCookie, Response, Session,
12};
13use rand::rngs::OsRng;
14use rand::{rand_core, TryRngCore};
15use tor_interface::tor_crypto::*;
16
17use crate::ascii_string::*;
19use crate::gosling::*;
20
21#[derive(thiserror::Error, Debug)]
26pub enum Error {
27 #[error("failed to convert x25519 private key to ed25519 private key")]
28 ClientCreationFailed(#[source] tor_interface::tor_crypto::Error),
29
30 #[error("HonkRPC method failed: {0}")]
31 HonkRPCFailure(#[from] honk_rpc::honk_rpc::Error),
32
33 #[error("client received unexpected response: {0}")]
34 UnexpectedResponseReceived(String),
35
36 #[error("client is in invalid state: {0}")]
37 InvalidState(String),
38
39 #[error("incorrect usage: {0}")]
40 IncorrectUsage(String),
41
42 #[error("provided endpoint challenge response too large; encoded size would be {0} but session's maximum honk-rpc message size is {1}")]
43 EndpointChallengeResponseTooLarge(usize, usize),
44
45 #[error("OsRng::try_fill_bytes() failed: {0}")]
46 OsRngTryFillBytesFailure(#[from] rand_core::OsError),
47}
48
49pub(crate) enum IdentityClientEvent {
50 ChallengeReceived {
51 endpoint_challenge: bson::document::Document,
52 },
53 HandshakeCompleted {
54 identity_service_id: V3OnionServiceId,
55 endpoint_service_id: V3OnionServiceId,
56 endpoint_name: String,
57 client_auth_private_key: X25519PrivateKey,
58 },
59}
60
61#[derive(Debug, PartialEq)]
62pub(crate) enum IdentityClientState {
63 BeginHandshake,
64 WaitingForChallenge,
65 WaitingForChallengeResponse,
66 WaitingForChallengeVerification,
67 HandshakeComplete,
68}
69
70pub(crate) struct IdentityClient {
75 rpc: Session<TcpStream>,
77 server_service_id: V3OnionServiceId,
78 requested_endpoint: AsciiString,
79 client_service_id: V3OnionServiceId,
80 client_identity_ed25519_private: Ed25519PrivateKey,
81 client_authorization_key_private: X25519PrivateKey,
82 client_authorization_signing_key_private: (Ed25519PrivateKey, SignBit),
83
84 state: IdentityClientState,
86 begin_handshake_request_cookie: Option<RequestCookie>,
87 server_cookie: Option<ServerCookie>,
88 endpoint_challenge_response: Option<bson::document::Document>,
89 send_response_request_cookie: Option<RequestCookie>,
90}
91
92impl IdentityClient {
93 fn get_state(&self) -> String {
94 format!("{{ state: {:?}, begin_handshake_request_cookie: {:?}, server_cookie: {:?}, endpoint_challenge_response: {:?}, send_response_request_cookie: {:?} }}", self.state, self.begin_handshake_request_cookie, self.server_cookie, self.endpoint_challenge_response, self.send_response_request_cookie)
95 }
96
97 pub fn new(
98 rpc: Session<TcpStream>,
99 server_service_id: V3OnionServiceId,
100 requested_endpoint: AsciiString,
101 client_identity_ed25519_private: Ed25519PrivateKey,
102 client_authorization_key_private: X25519PrivateKey,
103 ) -> Result<Self, Error> {
104 Ok(Self {
105 rpc,
106 server_service_id,
107 requested_endpoint,
108 client_service_id: V3OnionServiceId::from_private_key(&client_identity_ed25519_private),
109 client_identity_ed25519_private,
110 client_authorization_signing_key_private: Ed25519PrivateKey::from_private_x25519(
111 &client_authorization_key_private,
112 )
113 .map_err(Error::ClientCreationFailed)?,
114 client_authorization_key_private,
115
116 state: IdentityClientState::BeginHandshake,
117 begin_handshake_request_cookie: None,
118 server_cookie: None,
119 send_response_request_cookie: None,
120 endpoint_challenge_response: None,
121 })
122 }
123
124 pub fn update(&mut self) -> Result<Option<IdentityClientEvent>, Error> {
125 if self.state == IdentityClientState::HandshakeComplete {
126 return Err(Error::IncorrectUsage("update() may not be called after HandshakeComplete has been returned from previous update() call".to_string()));
127 }
128
129 self.rpc.update(None)?;
131
132 match (
134 &self.state,
135 self.begin_handshake_request_cookie,
136 self.server_cookie,
137 self.endpoint_challenge_response.take(),
138 self.send_response_request_cookie,
139 ) {
140 (
142 &IdentityClientState::BeginHandshake,
143 None, None, None, None, ) => {
148 self.begin_handshake_request_cookie = Some(self.rpc.client_call(
149 "gosling_identity",
150 "begin_handshake",
151 0,
152 doc! {
153 "version" : bson::Bson::String(GOSLING_PROTOCOL_VERSION.to_string()),
154 "client_identity" : bson::Bson::String(self.client_service_id.to_string()),
155 "endpoint" : bson::Bson::String(self.requested_endpoint.clone().to_string()),
156 },
157 )?);
158 self.state = IdentityClientState::WaitingForChallenge;
159 }
160 (
161 &IdentityClientState::WaitingForChallenge,
162 Some(begin_handshake_request_cookie),
163 None, None, None, ) => {
167 if let Some(response) = self.rpc.client_next_response() {
168 let mut response = match response {
170 Response::Pending { cookie } => {
171 if cookie != begin_handshake_request_cookie {
172 return Err(Error::UnexpectedResponseReceived(
173 "received unexpected pending response".to_string(),
174 ));
175 }
176 return Ok(None);
177 }
178 Response::Error { cookie, error_code } => {
179 if cookie != begin_handshake_request_cookie {
180 return Err(Error::UnexpectedResponseReceived(format!(
181 "received unexpected error response; rpc error_code: {}",
182 error_code
183 )));
184 }
185 return Err(Error::UnexpectedResponseReceived(format!(
186 "received unexpected rpc error_code: {}",
187 error_code
188 )));
189 }
190 Response::Success { cookie, result } => {
191 if cookie != begin_handshake_request_cookie {
192 return Err(Error::UnexpectedResponseReceived(
193 "received unexpected success response".to_string(),
194 ));
195 }
196 match result {
197 Some(Bson::Document(result)) => result,
198 _ => {
199 return Err(Error::UnexpectedResponseReceived(
200 "begin_handshake() response is unexpected bson type"
201 .to_string(),
202 ))
203 }
204 }
205 }
206 };
207
208 self.server_cookie = match response.get("server_cookie") {
210 Some(Bson::Binary(Binary {
211 subtype: BinarySubtype::Generic,
212 bytes: server_cookie,
213 })) => match server_cookie.clone().try_into() {
214 Ok(server_cookie) => Some(server_cookie),
215 Err(_) => {
216 return Err(Error::UnexpectedResponseReceived(format!(
217 "unable to convert '{:?}' to server cookie",
218 server_cookie
219 )))
220 }
221 },
222 Some(_) => {
223 return Err(Error::UnexpectedResponseReceived(
224 "server_cookie is unxpected bson type".to_string(),
225 ))
226 }
227 None => {
228 return Err(Error::UnexpectedResponseReceived(
229 "missing server_cookie".to_string(),
230 ))
231 }
232 };
233
234 let endpoint_challenge = match response.get_mut("endpoint_challenge") {
236 Some(Bson::Document(endpoint_challenge)) => {
237 std::mem::take(endpoint_challenge)
238 }
239 Some(_) => {
240 return Err(Error::UnexpectedResponseReceived(
241 "endpoint challenge is unexpected bson type".to_string(),
242 ))
243 }
244 None => {
245 return Err(Error::UnexpectedResponseReceived(
246 "missing endpoint_challenge".to_string(),
247 ))
248 }
249 };
250
251 self.state = IdentityClientState::WaitingForChallengeResponse;
252 return Ok(Some(IdentityClientEvent::ChallengeReceived {
253 endpoint_challenge,
254 }));
255 }
256 }
257 (
258 &IdentityClientState::WaitingForChallengeResponse,
259 Some(_begin_handshake_request_cookie),
260 Some(_server_cookie),
261 None, None, ) => {
264 }
266 (
267 &IdentityClientState::WaitingForChallengeResponse,
268 Some(_begin_handshake_request_cookie),
269 Some(server_cookie),
270 Some(endpoint_challenge_response),
271 None,
272 ) => {
273 let mut client_cookie: ClientCookie = Default::default();
275 OsRng.try_fill_bytes(&mut client_cookie)?;
276 let client_cookie = client_cookie;
277
278 let client_identity_proof = build_client_proof(
280 DomainSeparator::GoslingIdentity,
281 &self.requested_endpoint,
282 &self.client_service_id,
283 &self.server_service_id,
284 &client_cookie,
285 &server_cookie,
286 );
287 let client_identity_proof_signature = self
288 .client_identity_ed25519_private
289 .sign_message(&client_identity_proof);
290
291 let client_authorization_key =
293 X25519PublicKey::from_private_key(&self.client_authorization_key_private);
294
295 let client_identity = self.client_service_id.to_string();
297 let (client_authorization_signature, signbit) = (
298 self.client_authorization_signing_key_private
299 .0
300 .sign_message(client_identity.as_bytes()),
301 self.client_authorization_signing_key_private.1,
302 );
303
304 let args = doc! {
306 "client_cookie" : bson::Bson::Binary(bson::Binary{subtype: BinarySubtype::Generic, bytes: client_cookie.to_vec()}),
307 "client_identity_proof_signature" : bson::Bson::Binary(bson::Binary{subtype: BinarySubtype::Generic, bytes: client_identity_proof_signature.to_bytes().to_vec()}),
308 "client_authorization_key" : bson::Bson::Binary(bson::Binary{subtype: BinarySubtype::Generic, bytes: client_authorization_key.as_bytes().to_vec()}),
309 "client_authorization_key_signbit" : bson::Bson::Boolean(signbit.into()),
310 "client_authorization_signature" : bson::Bson::Binary(bson::Binary{subtype: BinarySubtype::Generic, bytes: client_authorization_signature.to_bytes().to_vec()}),
311 "challenge_response" : endpoint_challenge_response,
312 };
313
314 self.send_response_request_cookie =
316 Some(
317 self.rpc
318 .client_call("gosling_identity", "send_response", 0, args)?,
319 );
320 self.state = IdentityClientState::WaitingForChallengeVerification;
321 }
322 (
323 &IdentityClientState::WaitingForChallengeVerification,
324 Some(_begin_handshake_request_cookie),
325 Some(_server_cookie),
326 None, Some(send_response_request_cookie),
328 ) => {
329 if let Some(response) = self.rpc.client_next_response() {
330 let endpoint_service_id = match response {
331 Response::Pending { cookie } => {
332 if cookie == send_response_request_cookie {
333 return Ok(None);
334 } else {
335 return Err(Error::UnexpectedResponseReceived(
336 "received unexpectd pending response".to_string(),
337 ));
338 }
339 }
340 Response::Error { cookie, error_code } => {
341 if cookie == send_response_request_cookie {
342 return Err(Error::UnexpectedResponseReceived(format!(
343 "received unexpected error response; rpc error_code: {}",
344 error_code
345 )));
346 } else {
347 return Err(Error::UnexpectedResponseReceived(format!(
348 "received unexpected rpc error_code: {}",
349 error_code
350 )));
351 }
352 }
353 Response::Success { cookie, result } => {
354 if cookie == send_response_request_cookie {
355 match result {
356 Some(Bson::String(endpoint_service_id)) => {
357 match V3OnionServiceId::from_string(&endpoint_service_id) {
358 Ok(endpoint_service_id) => endpoint_service_id,
359 Err(_) => return Err(Error::UnexpectedResponseReceived(format!("unable to parse received endpoint service id '{}' as v3 onion service id", endpoint_service_id))),
360 }
361 }
362 _ => {
363 return Err(Error::UnexpectedResponseReceived(
364 "endpoint service id is unexpected bson type".to_string(),
365 ))
366 }
367 }
368 } else {
369 return Err(Error::UnexpectedResponseReceived(
370 "received unexpected success response".to_string(),
371 ));
372 }
373 }
374 };
375 self.state = IdentityClientState::HandshakeComplete;
376 return Ok(Some(IdentityClientEvent::HandshakeCompleted {
377 identity_service_id: self.server_service_id.clone(),
378 endpoint_service_id,
379 endpoint_name: self.requested_endpoint.clone().to_string(),
380 client_auth_private_key: self.client_authorization_key_private.clone(),
381 }));
382 }
383 }
384 _ => {
385 return Err(Error::InvalidState(self.get_state()));
386 }
387 }
388 Ok(None)
389 }
390
391 pub fn send_response(
392 &mut self,
393 challenge_response: bson::document::Document,
394 ) -> Result<(), Error> {
395 match (
396 &self.state,
397 self.begin_handshake_request_cookie,
398 self.server_cookie,
399 self.endpoint_challenge_response.as_ref(),
400 self.send_response_request_cookie,
401 ) {
402 (&IdentityClientState::WaitingForChallengeResponse,
403 Some(_begin_handshake_request_cookie),
404 Some(_server_cookie),
405 None, None ) => {
408 let arguments = doc!{
411 "client_cookie" : Bson::Binary(Binary{subtype: BinarySubtype::Generic, bytes: [0u8; CLIENT_COOKIE_SIZE].to_vec()}),
412 "client_identity_proof_signature" : Bson::Binary(Binary{subtype: BinarySubtype::Generic, bytes: [0u8; ED25519_SIGNATURE_SIZE].to_vec()}),
413 "client_authorization_key" : Bson::Binary(Binary{subtype: BinarySubtype::Generic, bytes: [0u8; X25519_PUBLIC_KEY_SIZE].to_vec()}),
414 "client_authorization_key_signbit" : Bson::Boolean(false),
415 "client_authorization_signature" : Bson::Binary(Binary{subtype: BinarySubtype::Generic, bytes: [0u8; ED25519_SIGNATURE_SIZE].to_vec()}),
416 "challenge_response" : challenge_response.clone(),
417 };
418 let request_section_size = get_request_section_size(Some(0i64), Some("gosling_identity".to_string()), "send_response".to_string(), Some(0i32), Some(arguments))?;
419 let message_size = get_message_overhead()? + request_section_size;
420 let max_message_size = self.rpc.get_max_message_size();
421 if message_size > max_message_size {
422 Err(Error::EndpointChallengeResponseTooLarge(message_size, max_message_size))
423 } else {
424 self.endpoint_challenge_response = Some(challenge_response);
425 Ok(())
426 }
427 }
428 _ => Err(Error::IncorrectUsage("send_response() may only be called after ChallengeReceived event has been returned from update(), and it may only be called once".to_string()))
429 }
430 }
431}