| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 34 |
#[derive(Clone, Debug)] |
| 35 |
pub struct Caller { |
| 36 |
pub principal: Principal, |
| 37 |
pub auth: Auth, |
| 38 |
|
| 39 |
|
| 40 |
pub learn_uid: Option<u32>, |
| 41 |
} |
| 42 |
|
| 43 |
|
| 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 |
} |