arti_rpcserver/connection/auth/
cookie.rs

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
5use std::sync::{Arc, Mutex, Weak};
6
7use derive_deftly::Deftly;
8use tor_rpc_connect::auth::{
9    cookie::{Cookie, CookieAuthMac, CookieAuthNonce},
10    RpcAuth,
11};
12use tor_rpcbase::{self as rpc, templates::*};
13
14use crate::{Connection, RpcMgr};
15
16use 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"))]
36struct CookieBegin {
37    /// A client-selected random nonce.
38    ///
39    /// Used as input to the `server_mac` calculation
40    client_nonce: CookieAuthNonce,
41}
42impl 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)]
49struct 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)]
73struct 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"))]
107struct 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
114impl 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.
120async 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`]
167async 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
203rpc::static_rpc_invoke_fn! {
204    cookie_begin;
205    cookie_continue;
206}