1
//! Authentication where both parties prove the ability to read a "cookie" file from disk.
2
//!
3
//! For full documentation of the protocol, see `rpc-cookie-sketch.md`
4

            
5
use std::sync::{Arc, Mutex, Weak};
6

            
7
use derive_deftly::Deftly;
8
use tor_rpc_connect::auth::{
9
    cookie::{Cookie, CookieAuthMac, CookieAuthNonce},
10
    RpcAuth,
11
};
12
use tor_rpcbase::{self as rpc, templates::*};
13

            
14
use crate::{Connection, RpcMgr};
15

            
16
use super::{AuthenticateReply, AuthenticationFailure};
17

            
18
/// Begin authenticating on an RPC connection, using Cookie authentication.
19
///
20
/// In cookie authentication, both parties prove knowledge of a
21
/// shared secret, written to a file on disk.  This method
22
/// does not prevent MITM attacks on its own.
23
///
24
/// When cookie authentication is in use, clients use this method
25
/// to begin cookie authentication by telling the RPC server
26
/// a temporary nonce.
27
///
28
/// You typically won't need to invoke this method yourself;
29
/// instead, your RPC library (such as `arti-rpc-client-core`)
30
/// should handle it for you.
31
///
32
/// See `rpc-cookie-sketch.md` for full details of this protocol.
33
#[derive(Debug, serde::Deserialize, Deftly)]
34
#[derive_deftly(DynMethod)]
35
#[deftly(rpc(method_name = "auth:cookie_begin"))]
36
struct CookieBegin {
37
    /// A client-selected random nonce.
38
    ///
39
    /// Used as input to the `server_mac` calculation
40
    client_nonce: CookieAuthNonce,
41
}
42
impl rpc::RpcMethod for CookieBegin {
43
    type Output = CookieBeginReply;
44
    type Update = rpc::NoUpdates;
45
}
46

            
47
/// An RPC server's response to an `auth:cookie_begin` request.
48
#[derive(Debug, serde::Serialize)]
49
struct CookieBeginReply {
50
    /// Handle to an object to use for the client's subsequent `cookie_continue`.
51
    cookie_auth: rpc::ObjectId,
52
    /// The address that the server believes it is listening on.
53
    ///
54
    /// The client should verify that this is the exact string encoded
55
    /// in the connect point that it's trying to use.
56
    server_addr: String,
57
    /// A MAC proving that the server knows the secret cookie.
58
    ///
59
    /// 32 bytes long, encoded in 64 bytes of hexadecimal.  Case-insensitive.
60
    server_mac: CookieAuthMac,
61
    /// A secret nonce chosen by the server.
62
    ///
63
    /// 32 bytes long, encoded in 64 bytes of hexadecimal.  Case-insensitive.
64
    server_nonce: CookieAuthNonce,
65
}
66

            
67
/// An in-progress cookie authentication attempt.
68
///
69
/// This object is returned by `auth:cookie_begin`;
70
/// it can be used a single time with `auth:cookie_continue` to finish authentication.
71
#[derive(Deftly)]
72
#[derive_deftly(rpc::Object)]
73
struct CookieAuthInProgress {
74
    /// The cookie we're using to check the client's authentication.
75
    cookie: Arc<Cookie>,
76
    /// The RPC manager we'll use, if successful, to create a session.
77
    mgr: Weak<RpcMgr>,
78
    /// The nonce that the client sent us.
79
    client_nonce: CookieAuthNonce,
80
    /// The nonce that we sent to the client.
81
    ///
82
    /// If this is None, then the client already authenticated (or failed to authenticate) using
83
    /// this object once, and this object can no longer be used.
84
    ///
85
    /// (It is okay in our protocol for the client to authenticate more than once on the same
86
    /// connection, but we want to ensure that we use a fresh nonce each time.)
87
    server_nonce: Mutex<Option<CookieAuthNonce>>,
88
    /// The address that we believe we're listening on.
89
    server_addr: String,
90
}
91

            
92
/// Finish cookie authentication, returning a new RPC Session.
93
///
94
/// You typically won't need to invoke this method yourself;
95
/// instead, your RPC library (such as `arti-rpc-client-core`)
96
/// should handle it for you.
97
///
98
/// After invoking this method, the RPC library should use `rpc:release`
99
/// to drop its reference to the `CookieAuthInProgress`:
100
/// remember, as a rule,
101
/// RPC methods other than `rpc:release` do not consume the objects they are invoked on.
102
///
103
/// See `rpc-cookie-sketch.md` for full details of this protocol.
104
#[derive(Debug, serde::Deserialize, Deftly)]
105
#[derive_deftly(DynMethod)]
106
#[deftly(rpc(method_name = "auth:cookie_continue"))]
107
struct CookieContinue {
108
    /// MAC to prove knowledge of the secret cookie.
109
    ///
110
    /// 32 bytes long, encoded in 64 bytes of hexadecimal.  Case-insensitive.
111
    client_mac: CookieAuthMac,
112
}
113

            
114
impl rpc::RpcMethod for CookieContinue {
115
    type Output = AuthenticateReply;
116
    type Update = rpc::NoUpdates;
117
}
118

            
119
/// Invoke the `auth:cookie_begin` method on a connection.
120
async fn cookie_begin(
121
    unauth: Arc<Connection>,
122
    method: Box<CookieBegin>,
123
    ctx: Arc<dyn rpc::Context>,
124
) -> Result<CookieBeginReply, rpc::RpcError> {
125
    // Make sure that we actually want cookie authentication.
126
    let (cookie, server_addr) = match &unauth.require_auth {
127
        RpcAuth::Cookie {
128
            secret,
129
            server_address,
130
            ..
131
        } => (
132
            secret.load().map_err(|_| {
133
                // This is an internal error, since server cookies are always preloaded.
134
                rpc::RpcError::new(
135
                    "Somehow had an unloadable cookie".into(),
136
                    rpc::RpcErrorKind::InternalError,
137
                )
138
            })?,
139
            server_address.clone(),
140
        ),
141
        _ => return Err(AuthenticationFailure::IncorrectMethod.into()),
142
    };
143
    let mut rng = rand::rng();
144

            
145
    let server_nonce = CookieAuthNonce::new(&mut rng);
146

            
147
    let server_mac = cookie.server_mac(&method.client_nonce, &server_nonce, server_addr.as_str());
148

            
149
    let auth_in_progress = Arc::new(CookieAuthInProgress {
150
        cookie,
151
        mgr: unauth.mgr.clone(),
152
        client_nonce: method.client_nonce,
153
        server_nonce: Mutex::new(Some(server_nonce.clone())),
154
        server_addr: server_addr.clone(),
155
    });
156
    let cookie_auth = ctx.register_owned(auth_in_progress);
157

            
158
    Ok(CookieBeginReply {
159
        cookie_auth,
160
        server_addr,
161
        server_mac,
162
        server_nonce,
163
    })
164
}
165

            
166
/// Invoke the `auth:cookie_continue` method on a [`CookieAuthInProgress`]
167
async fn cookie_continue(
168
    in_progress: Arc<CookieAuthInProgress>,
169
    method: Box<CookieContinue>,
170
    ctx: Arc<dyn rpc::Context>,
171
) -> Result<AuthenticateReply, rpc::RpcError> {
172
    // Make sure we haven't gotten another one of these.
173
    let Some(server_nonce) = in_progress
174
        .server_nonce
175
        .lock()
176
        .expect("lock poisoned")
177
        .take()
178
    else {
179
        return Err(AuthenticationFailure::CookieNonceReused.into());
180
    };
181

            
182
    let expected_client_mac = in_progress.cookie.client_mac(
183
        &in_progress.client_nonce,
184
        &server_nonce,
185
        &in_progress.server_addr,
186
    );
187

            
188
    if expected_client_mac != method.client_mac {
189
        return Err(AuthenticationFailure::IncorrectAuthentication.into());
190
    }
191

            
192
    let mgr = in_progress
193
        .mgr
194
        .upgrade()
195
        .ok_or(AuthenticationFailure::ShuttingDown)?;
196
    let auth = &super::RpcAuthentication {};
197
    let session = mgr.create_session(auth);
198
    let session = ctx.register_owned(session);
199

            
200
    Ok(AuthenticateReply { session })
201
}
202

            
203
rpc::static_rpc_invoke_fn! {
204
    cookie_begin;
205
    cookie_continue;
206
}