1
//! Authentication for RpcConn.
2

            
3
use serde::{Deserialize, Serialize};
4
use tor_rpc_connect::auth::cookie::{Cookie, CookieAuthMac, CookieAuthNonce};
5

            
6
use crate::msgs::{request::Request, ObjectId};
7

            
8
use super::{ConnectError, RpcConn};
9

            
10
/// Arguments to an `auth:authenticate` request.
11
#[derive(Serialize, Debug)]
12
struct 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)]
18
struct 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)]
25
struct 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)]
31
struct 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)]
44
struct CookieContinueParams {
45
    /// Make to prove our knowledge of the cookie.
46
    client_mac: CookieAuthMac,
47
}
48

            
49
impl 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
}