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::{ObjectId, request::Request};
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
/// Response to an `auth:cookie_begin` request.
31
#[derive(Deserialize, Debug)]
32
struct CookieBeginReply {
33
    /// Temporary ID to use while authenticating.
34
    cookie_auth: ObjectId,
35
    /// Address that the server thinks it's listening on.
36
    server_addr: String,
37
    /// MAC returned by the server to prove knowledge of the cookie.
38
    server_mac: CookieAuthMac,
39
    /// Server-selected nonce to use while we prove knowledge of the cookie.
40
    server_nonce: CookieAuthNonce,
41
}
42

            
43
/// Arguments to an `auth:cookie_begin` request.
44
#[derive(Serialize, Debug)]
45
struct CookieContinueParams {
46
    /// Make to prove our knowledge of the cookie.
47
    client_mac: CookieAuthMac,
48
}
49

            
50
impl RpcConn {
51
    /// Try to negotiate "inherent" authentication, using the provided scheme name.
52
    ///
53
    /// (Inherent authentication is available whenever the client proves that they
54
    /// are authorized through being able to connect to Arti at all.  Examples
55
    /// include connecting to a unix domain socket, and an in-process Arti implementation.)
56
    pub(crate) fn authenticate_inherent(
57
        &self,
58
        scheme_name: &str,
59
    ) -> Result<ObjectId, ConnectError> {
60
        let r: Request<AuthParams> = Request::new(
61
            ObjectId::connection_id(),
62
            "auth:authenticate",
63
            AuthParams {
64
                scheme: scheme_name,
65
            },
66
        );
67
        let authenticated: AuthenticatedReply = self
68
            .execute_internal(&r.encode()?)?
69
            .map_err(ConnectError::AuthenticationFailed)?;
70

            
71
        Ok(authenticated.session)
72
    }
73

            
74
    /// Try to negotiate "cookie" authentication, using the provided cookie and server address.
75
    pub(crate) fn authenticate_cookie(
76
        &self,
77
        cookie: &Cookie,
78
        server_addr: &str,
79
    ) -> Result<ObjectId, ConnectError> {
80
        // This protocol is documented in `rpc-cookie-sketch.md`.
81
        let client_nonce = CookieAuthNonce::new(&mut rand::rng());
82

            
83
        let cookie_begin: Request<CookieBeginParams> = Request::new(
84
            ObjectId::connection_id(),
85
            "auth:cookie_begin",
86
            CookieBeginParams {
87
                client_nonce: client_nonce.clone(),
88
            },
89
        );
90
        let reply: CookieBeginReply = self
91
            .execute_internal(&cookie_begin.encode()?)?
92
            .map_err(ConnectError::AuthenticationFailed)?;
93

            
94
        if server_addr != reply.server_addr {
95
            return Err(ConnectError::ServerAddressMismatch {
96
                ours: server_addr.into(),
97
                theirs: reply.server_addr,
98
            });
99
        }
100

            
101
        let expected_server_mac =
102
            cookie.server_mac(&client_nonce, &reply.server_nonce, server_addr);
103
        if reply.server_mac != expected_server_mac {
104
            return Err(ConnectError::CookieMismatch);
105
        }
106

            
107
        let client_mac = cookie.client_mac(&client_nonce, &reply.server_nonce, server_addr);
108
        let cookie_auth_obj = reply.cookie_auth.clone();
109
        let cookie_continue = Request::new(
110
            cookie_auth_obj.clone(),
111
            "auth:cookie_continue",
112
            CookieContinueParams { client_mac },
113
        );
114
        let authenticated: AuthenticatedReply = self
115
            .execute_internal(&cookie_continue.encode()?)?
116
            .map_err(ConnectError::AuthenticationFailed)?;
117

            
118
        // Drop the cookie_auth_obj: we don't need it now that we have authenticated.
119
        self.release_obj(cookie_auth_obj)?;
120

            
121
        Ok(authenticated.session)
122
    }
123
}