//! Authentication where both parties prove the ability to read a "cookie" file from disk. //! //! For full documentation of the protocol, see `rpc-cookie-sketch.md` use std::sync::{Arc, Mutex, Weak}; use derive_deftly::Deftly; use tor_rpc_connect::auth::{ RpcAuth, cookie::{Cookie, CookieAuthMac, CookieAuthNonce}, }; use tor_rpcbase::{self as rpc, templates::*}; use crate::Connection; use super::{AuthenticateReply, AuthenticationFailure}; /// Begin authenticating on an RPC connection, using Cookie authentication. /// /// In cookie authentication, both parties prove knowledge of a /// shared secret, written to a file on disk. This method /// does not prevent MITM attacks on its own. /// /// When cookie authentication is in use, clients use this method /// to begin cookie authentication by telling the RPC server /// a temporary nonce. /// /// You typically won't need to invoke this method yourself; /// instead, your RPC library (such as `arti-rpc-client-core`) /// should handle it for you. /// /// See `rpc-cookie-sketch.md` for full details of this protocol. #[derive(Debug, serde::Deserialize, Deftly)] #[derive_deftly(DynMethod)] #[deftly(rpc(method_name = "auth:cookie_begin"))] struct CookieBegin { /// A client-selected random nonce. /// /// Used as input to the `server_mac` calculation client_nonce: CookieAuthNonce, } impl rpc::RpcMethod for CookieBegin { type Output = CookieBeginReply; type Update = rpc::NoUpdates; } /// An RPC server's response to an `auth:cookie_begin` request. #[derive(Debug, serde::Serialize)] struct CookieBeginReply { /// Handle to an object to use for the client's subsequent `cookie_continue`. cookie_auth: rpc::ObjectId, /// The address that the server believes it is listening on. /// /// The client should verify that this is the exact string encoded /// in the connect point that it's trying to use. server_addr: String, /// A MAC proving that the server knows the secret cookie. /// /// 32 bytes long, encoded in 64 bytes of hexadecimal. Case-insensitive. server_mac: CookieAuthMac, /// A secret nonce chosen by the server. /// /// 32 bytes long, encoded in 64 bytes of hexadecimal. Case-insensitive. server_nonce: CookieAuthNonce, } /// An in-progress cookie authentication attempt. /// /// This object is returned by `auth:cookie_begin`; /// it can be used a single time with `auth:cookie_continue` to finish authentication. #[derive(Deftly)] #[derive_deftly(rpc::Object)] struct CookieAuthInProgress { /// The cookie we're using to check the client's authentication. cookie: Arc, /// The RPC connection we'll use, if successful, to create a session. conn: Weak, /// The nonce that the client sent us. client_nonce: CookieAuthNonce, /// The nonce that we sent to the client. /// /// If this is None, then the client already authenticated (or failed to authenticate) using /// this object once, and this object can no longer be used. /// /// (It is okay in our protocol for the client to authenticate more than once on the same /// connection, but we want to ensure that we use a fresh nonce each time.) server_nonce: Mutex>, /// The address that we believe we're listening on. server_addr: String, } /// Finish cookie authentication, returning a new RPC Session. /// /// You typically won't need to invoke this method yourself; /// instead, your RPC library (such as `arti-rpc-client-core`) /// should handle it for you. /// /// After invoking this method, the RPC library should use `rpc:release` /// to drop its reference to the `CookieAuthInProgress`: /// remember, as a rule, /// RPC methods other than `rpc:release` do not consume the objects they are invoked on. /// /// See `rpc-cookie-sketch.md` for full details of this protocol. #[derive(Debug, serde::Deserialize, Deftly)] #[derive_deftly(DynMethod)] #[deftly(rpc(method_name = "auth:cookie_continue"))] struct CookieContinue { /// MAC to prove knowledge of the secret cookie. /// /// 32 bytes long, encoded in 64 bytes of hexadecimal. Case-insensitive. client_mac: CookieAuthMac, } impl rpc::RpcMethod for CookieContinue { type Output = AuthenticateReply; type Update = rpc::NoUpdates; } /// Invoke the `auth:cookie_begin` method on a connection. async fn cookie_begin( unauth: Arc, method: Box, ctx: Arc, ) -> Result { // Make sure that we actually want cookie authentication. let (cookie, server_addr) = match &unauth.require_auth { RpcAuth::Cookie { secret, server_address, .. } => ( secret.load().map_err(|_| { // This is an internal error, since server cookies are always preloaded. rpc::RpcError::new( "Somehow had an unloadable cookie".into(), rpc::RpcErrorKind::InternalError, ) })?, server_address.clone(), ), _ => return Err(AuthenticationFailure::IncorrectMethod.into()), }; let mut rng = rand::rng(); let server_nonce = CookieAuthNonce::new(&mut rng); let server_mac = cookie.server_mac(&method.client_nonce, &server_nonce, server_addr.as_str()); let auth_in_progress = Arc::new(CookieAuthInProgress { cookie, conn: Arc::downgrade(&unauth), client_nonce: method.client_nonce, server_nonce: Mutex::new(Some(server_nonce.clone())), server_addr: server_addr.clone(), }); let cookie_auth = ctx.register_owned(auth_in_progress); Ok(CookieBeginReply { cookie_auth, server_addr, server_mac, server_nonce, }) } /// Invoke the `auth:cookie_continue` method on a [`CookieAuthInProgress`] async fn cookie_continue( in_progress: Arc, method: Box, ctx: Arc, ) -> Result { // Make sure we haven't gotten another one of these. let Some(server_nonce) = in_progress .server_nonce .lock() .expect("lock poisoned") .take() else { return Err(AuthenticationFailure::CookieNonceReused.into()); }; let expected_client_mac = in_progress.cookie.client_mac( &in_progress.client_nonce, &server_nonce, &in_progress.server_addr, ); if expected_client_mac != method.client_mac { return Err(AuthenticationFailure::IncorrectAuthentication.into()); } let conn = in_progress .conn .upgrade() .ok_or(AuthenticationFailure::ShuttingDown)?; let auth = &super::RpcAuthentication {}; let session = conn.create_session(auth)?; let session = ctx.register_owned(session); Ok(AuthenticateReply { session }) } rpc::static_rpc_invoke_fn! { cookie_begin; cookie_continue; }