gosling/
identity_client.rs

1// standard
2use std::clone::Clone;
3use std::convert::TryInto;
4use std::net::TcpStream;
5
6// extern crates
7use 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
17// internal crates
18use crate::ascii_string::*;
19use crate::gosling::*;
20
21//
22// Identity Client
23//
24
25#[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
70//
71// An identity client object used for connecting
72// to an identity server
73//
74pub(crate) struct IdentityClient {
75    // session data
76    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 machine data
85    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        // update our rpc session
130        self.rpc.update(None)?;
131
132        // client state machine
133        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            // send initial handshake request
141            (
142                &IdentityClientState::BeginHandshake,
143                None, // begin_handshake_request_cookie
144                None, // server_cookie
145                None, // endpoint_challenge_response
146                None, // send_response_request_cookie
147            ) => {
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, // server_cookie
164                None, // endpoint_challenge_response
165                None, // send_response_request_cookie
166            ) => {
167                if let Some(response) = self.rpc.client_next_response() {
168                    // check for response for the begin_handshake() call
169                    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                    // save off the server cookie
209                    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                    // get the endpoint challenge
235                    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, // endpoint_challenge_response
262                None, // send_response_request_cookie
263            ) => {
264                // no-op, waiting for response for challenge response from caller
265            }
266            (
267                &IdentityClientState::WaitingForChallengeResponse,
268                Some(_begin_handshake_request_cookie),
269                Some(server_cookie),
270                Some(endpoint_challenge_response),
271                None,
272            ) => {
273                // client_cookie
274                let mut client_cookie: ClientCookie = Default::default();
275                OsRng.try_fill_bytes(&mut client_cookie)?;
276                let client_cookie = client_cookie;
277
278                // client_identity_proof_signature
279                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                // client_authorization_key
292                let client_authorization_key =
293                    X25519PublicKey::from_private_key(&self.client_authorization_key_private);
294
295                // client_authorization_signature
296                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                // build our args object for rpc call
305                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                // make rpc call
315                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, // endpoint_challenge_response
327                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, // endpoint_challenge_response
406             None  // end_response_request_cookie
407            ) => {
408                // calculate required size of request message and ensure it fits our
409                // specified message size budget
410                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}