//! Authenticating a request (spec §8.5): a signature by a known key over the //! method, the body, a time within five minutes and a nonce not seen in //! that window, from a key the owner delegates to now, and, when the agent //! is bound to a Unix user, from that user. What it returns becomes the //! `auth` of every event the request causes. use crate::event::{Auth, Method}; use crate::principals::{Principal, Principals}; use crate::proof::{Proof, WINDOW_MS}; use std::collections::HashMap; use std::sync::Mutex; /// Why a request was refused. #[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum Refusal { #[error("the request is not signed")] Unsigned, #[error("the signature is for {0}, not this request")] WrongMethod(String), #[error("the request's time is {0} s from the server's: check the clock")] Stale(i64), #[error("the request's nonce was used before: a replay")] Replay, #[error("{0}")] NotAPrincipal(String), #[error("{0}")] BadSignature(String), /// The key is bound to another Unix user (logged as `guard.impersonation`). #[error("agent {agent}'s key is bound to uid {bound}, used from uid {from}")] WrongUid { agent: String, bound: u32, from: u32 }, /// The key is bound to a Unix user but came over TCP, where there is no /// peer uid to check (spec §8.6; logged as `guard.impersonation`). #[error("agent {agent}'s key is bound to uid {bound} and must come over the Unix socket, not the network")] BoundOverNetwork { agent: String, bound: u32 }, } /// The outcome: who, and the `auth` for their events. #[derive(Clone, Debug)] pub struct Caller { pub principal: Principal, pub auth: Auth, /// The agent asked for its uid to be learnt and has none yet: the /// caller records `agent.uid_bound` with this uid. pub learn_uid: Option, } /// Remembers nonces for the window. #[derive(Debug, Default)] pub struct Authenticator { nonces: Mutex>, } impl Authenticator { pub fn authenticate( &self, principals: &Principals, proof: Option<&Proof>, method: &str, body: &[u8], now_ms: u64, peer_uid: Option, ) -> Result { let proof = proof.ok_or(Refusal::Unsigned)?; if proof.method != method { return Err(Refusal::WrongMethod(proof.method.clone())); } let skew = proof.time_ms as i64 - now_ms as i64; if skew.unsigned_abs() > WINDOW_MS { return Err(Refusal::Stale(skew / 1000)); } let key = crate::identity::KeyId(proof.key()); let principal = principals.principal(&key, now_ms).map_err(Refusal::NotAPrincipal)?; let identity = principals.identity(&key).ok_or_else(|| Refusal::NotAPrincipal("an unknown key".into()))?; proof.verify(identity, Some(body)).map_err(Refusal::BadSignature)?; let mut learn_uid = None; if let Principal::Agent { name, uid, learn_uid: learn, .. } = &principal { match (uid, peer_uid) { (Some(bound), Some(from)) if *bound != from => return Err(Refusal::WrongUid { agent: name.clone(), bound: *bound, from }), (Some(bound), None) => return Err(Refusal::BoundOverNetwork { agent: name.clone(), bound: *bound }), (None, Some(from)) if *learn => learn_uid = Some(from), _ => {} } } { let mut seen = self.nonces.lock().unwrap(); seen.retain(|_, t| now_ms.saturating_sub(*t) <= 2 * WINDOW_MS); if seen.insert(proof.nonce, now_ms).is_some() { return Err(Refusal::Replay); } } Ok(Caller { principal, auth: Auth { key: Some(proof.key()), method: Method::Signature, assurance: 2, proof: Some(proof.encode()) }, learn_uid, }) } }