OXYGENHUB

Coverage / crates/spectra-core/src/authn.rs 97.6%

40 of 41 lines run, 2 of 6 functions · run #16 on main · run missed

1
//! Authenticating a request (spec §8.5): a signature by a known key over the
2
//! method, the body, a time within five minutes and a nonce not seen in
3
//! that window, from a key the owner delegates to now, and, when the agent
4
//! is bound to a Unix user, from that user. What it returns becomes the
5
//! `auth` of every event the request causes.
6
7
use crate::event::{Auth, Method};
8
use crate::principals::{Principal, Principals};
9
use crate::proof::{Proof, WINDOW_MS};
10
use std::collections::HashMap;
11
use std::sync::Mutex;
12
13
/// Why a request was refused.
14
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
15
pub enum Refusal {
16
    #[error("the request is not signed")]
17
    Unsigned,
18
    #[error("the signature is for {0}, not this request")]
19
    WrongMethod(String),
20
    #[error("the request's time is {0} s from the server's: check the clock")]
21
    Stale(i64),
22
    #[error("the request's nonce was used before: a replay")]
23
    Replay,
24
    #[error("{0}")]
25
    NotAPrincipal(String),
26
    #[error("{0}")]
27
    BadSignature(String),
28
    /// The key is bound to another Unix user (logged as `guard.impersonation`).
29
    #[error("agent {agent}'s key is bound to uid {bound}, used from uid {from}")]
30
    WrongUid { agent: String, bound: u32, from: u32 },
31
}
32
33
/// The outcome: who, and the `auth` for their events.
34
#[derive(Clone, Debug)]
35
pub struct Caller {
36
    pub principal: Principal,
37
    pub auth: Auth,
38
    /// The agent asked for its uid to be learnt and has none yet: the
39
    /// caller records `agent.uid_bound` with this uid.
40
    pub learn_uid: Option<u32>,
41
}
42
43
/// Remembers nonces for the window.
44
#[derive(Debug, Default)]
45
pub struct Authenticator {
46
    nonces: Mutex<HashMap<[u8; 16], u64>>,
47
}
48
49
impl Authenticator {
5084×
    pub fn authenticate(
5184×
        &self,
5284×
        principals: &Principals,
5384×
        proof: Option<&Proof>,
5484×
        method: &str,
5584×
        body: &[u8],
5684×
        now_ms: u64,
5784×
        peer_uid: Option<u32>,
5884×
    ) -> Result<Caller, Refusal> {
5984×
        let proof = proof.ok_or(Refusal::Unsigned)?;
6081×
        if proof.method != method {
611×
            return Err(Refusal::WrongMethod(proof.method.clone()));
6280×
        }
6380×
        let skew = proof.time_ms as i64 - now_ms as i64;
6480×
        if skew.unsigned_abs() > WINDOW_MS {
651×
            return Err(Refusal::Stale(skew / 1000));
6679×
        }
6779×
        let key = oxy_core::hash::ObjectRef(proof.key());
6879×
        let principal = principals.principal(&key, now_ms).map_err(Refusal::NotAPrincipal)?;
6977×
        let identity = principals.identity(&key).ok_or_else(|| Refusal::NotAPrincipal("an unknown key".into()))?;
7077×
        proof.verify(identity, Some(body)).map_err(Refusal::BadSignature)?;
7176×
        let mut learn_uid = None;
7276×
        if let Principal::Agent { name, uid, learn_uid: learn, .. } = &principal {
7363×
            match (uid, peer_uid) {
743×
                (Some(bound), Some(from)) if *bound != from => return Err(Refusal::WrongUid { agent: name.clone(), bound: *bound, from }),
751×
                (None, Some(from)) if *learn => learn_uid = Some(from),
7660×
                _ => {}
77
            }
7813×
        }
79
        {
8074×
            let mut seen = self.nonces.lock().unwrap();
81481×
            seen.retain(|_, t| now_ms.saturating_sub(*t) <= 2 * WINDOW_MS);
8274×
            if seen.insert(proof.nonce, now_ms).is_some() {
831×
                return Err(Refusal::Replay);
8473×
            }
85
        }
8673×
        Ok(Caller {
8773×
            principal,
8873×
            auth: Auth { key: Some(proof.key()), method: Method::Signature, assurance: 2, proof: Some(proof.encode()) },
8973×
            learn_uid,
9073×
        })
9184×
    }
92
}