OXYGENHUB
# 5d695d869fbf spectra / crates / spectra-core / src / authn.rs
3.4 KiB · Rust · last changed Identities instead of tokens: init, owned agents as delegates, verified replay of identities, request authentication, one writer (#4) 2 days ago source blame history raw
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 {
50
    pub fn authenticate(
51
        &self,
52
        principals: &Principals,
53
        proof: Option<&Proof>,
54
        method: &str,
55
        body: &[u8],
56
        now_ms: u64,
57
        peer_uid: Option<u32>,
58
    ) -> Result<Caller, Refusal> {
59
        let proof = proof.ok_or(Refusal::Unsigned)?;
60
        if proof.method != method {
61
            return Err(Refusal::WrongMethod(proof.method.clone()));
62
        }
63
        let skew = proof.time_ms as i64 - now_ms as i64;
64
        if skew.unsigned_abs() > WINDOW_MS {
65
            return Err(Refusal::Stale(skew / 1000));
66
        }
67
        let key = oxy_core::hash::ObjectRef(proof.key());
68
        let principal = principals.principal(&key, now_ms).map_err(Refusal::NotAPrincipal)?;
69
        let identity = principals.identity(&key).ok_or_else(|| Refusal::NotAPrincipal("an unknown key".into()))?;
70
        proof.verify(identity, Some(body)).map_err(Refusal::BadSignature)?;
71
        let mut learn_uid = None;
72
        if let Principal::Agent { name, uid, learn_uid: learn, .. } = &principal {
73
            match (uid, peer_uid) {
74
                (Some(bound), Some(from)) if *bound != from => return Err(Refusal::WrongUid { agent: name.clone(), bound: *bound, from }),
75
                (None, Some(from)) if *learn => learn_uid = Some(from),
76
                _ => {}
77
            }
78
        }
79
        {
80
            let mut seen = self.nonces.lock().unwrap();
81
            seen.retain(|_, t| now_ms.saturating_sub(*t) <= 2 * WINDOW_MS);
82
            if seen.insert(proof.nonce, now_ms).is_some() {
83
                return Err(Refusal::Replay);
84
            }
85
        }
86
        Ok(Caller {
87
            principal,
88
            auth: Auth { key: Some(proof.key()), method: Method::Signature, assurance: 2, proof: Some(proof.encode()) },
89
            learn_uid,
90
        })
91
    }
92
}