arti_rpc_client_core/conn/
auth.rs

1//! Authentication for RpcConn.
2
3use serde::{Deserialize, Serialize};
4use tor_rpc_connect::auth::cookie::{Cookie, CookieAuthMac, CookieAuthNonce};
5
6use crate::msgs::{request::Request, ObjectId};
7
8use super::{ConnectError, RpcConn};
9
10/// Arguments to an `auth:authenticate` request.
11#[derive(Serialize, Debug)]
12struct AuthParams<'a> {
13    /// The authentication scheme we are using.
14    scheme: &'a str,
15}
16/// Response to an `auth:authenticate` or `auth:cookie_continue` request.
17#[derive(Deserialize, Debug)]
18struct AuthenticatedReply {
19    /// A session object that we use to access the rest of Arti's functionality.
20    session: ObjectId,
21}
22
23/// Arguments to an `auth:cookie_begin` request.
24#[derive(Serialize, Debug)]
25struct CookieBeginParams {
26    /// Client-selected nonce; used while the server is proving knowledge of the cookie.
27    client_nonce: CookieAuthNonce,
28}
29
30#[derive(Deserialize, Debug)]
31struct CookieBeginReply {
32    /// Temporary ID to use while authenticating.
33    cookie_auth: ObjectId,
34    /// Address that the server thinks it's listening on.
35    server_addr: String,
36    /// MAC returned by the server to prove knowledge of the cookie.
37    server_mac: CookieAuthMac,
38    /// Server-selected nonce to use while we prove knowledge of the cookie.
39    server_nonce: CookieAuthNonce,
40}
41
42/// Arguments to an `auth:cookie_begin` request.
43#[derive(Serialize, Debug)]
44struct CookieContinueParams {
45    /// Make to prove our knowledge of the cookie.
46    client_mac: CookieAuthMac,
47}
48
49impl RpcConn {
50    /// Try to negotiate "inherent" authentication, using the provided scheme name.
51    ///
52    /// (Inherent authentication is available whenever the client proves that they
53    /// are authorized through being able to connect to Arti at all.  Examples
54    /// include connecting to a unix domain socket, and an in-process Arti implementation.)
55    pub(crate) fn authenticate_inherent(
56        &self,
57        scheme_name: &str,
58    ) -> Result<ObjectId, ConnectError> {
59        let r: Request<AuthParams> = Request::new(
60            ObjectId::connection_id(),
61            "auth:authenticate",
62            AuthParams {
63                scheme: scheme_name,
64            },
65        );
66        let authenticated: AuthenticatedReply = self
67            .execute_internal(&r.encode()?)?
68            .map_err(ConnectError::AuthenticationFailed)?;
69
70        Ok(authenticated.session)
71    }
72
73    /// Try to negotiate "cookie" authentication, using the provided cookie and server address.
74    pub(crate) fn authenticate_cookie(
75        &self,
76        cookie: &Cookie,
77        server_addr: &str,
78    ) -> Result<ObjectId, ConnectError> {
79        // This protocol is documented in `rpc-cookie-sketch.md`.
80        let client_nonce = CookieAuthNonce::new(&mut rand::rng());
81
82        let cookie_begin: Request<CookieBeginParams> = Request::new(
83            ObjectId::connection_id(),
84            "auth:cookie_begin",
85            CookieBeginParams {
86                client_nonce: client_nonce.clone(),
87            },
88        );
89        let reply: CookieBeginReply = self
90            .execute_internal(&cookie_begin.encode()?)?
91            .map_err(ConnectError::AuthenticationFailed)?;
92
93        if server_addr != reply.server_addr {
94            return Err(ConnectError::ServerAddressMismatch {
95                ours: server_addr.into(),
96                theirs: reply.server_addr,
97            });
98        }
99
100        let expected_server_mac =
101            cookie.server_mac(&client_nonce, &reply.server_nonce, server_addr);
102        if reply.server_mac != expected_server_mac {
103            return Err(ConnectError::CookieMismatch);
104        }
105
106        let client_mac = cookie.client_mac(&client_nonce, &reply.server_nonce, server_addr);
107        let cookie_auth_obj = reply.cookie_auth.clone();
108        let cookie_continue = Request::new(
109            cookie_auth_obj.clone(),
110            "auth:cookie_continue",
111            CookieContinueParams { client_mac },
112        );
113        let authenticated: AuthenticatedReply = self
114            .execute_internal(&cookie_continue.encode()?)?
115            .map_err(ConnectError::AuthenticationFailed)?;
116
117        // Drop the cookie_auth_obj: we don't need it now that we have authenticated.
118        self.release_obj(cookie_auth_obj)?;
119
120        Ok(authenticated.session)
121    }
122}