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}