1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! Credential verification for latency-critical services.
//!
//! On the development host (Apple M1 Pro), Tollgate's admission path costs
//! about 107 ns. Verifying a credential to reach it costs 800 ns to 2 µs —
//! seven to twenty times everything the rest of the stack does. Absolute
//! values are host-dependent; the ratio is the point. A library that tunes the 107 ns while leaving the 2 µs
//! to each embedder is tuning the wrong end, so this crate owns the
//! credential step too.
//!
//! It is split the way the problem is:
//!
//! - **[`CredentialVerifier`]** is the seam. Credential *schemes* differ per
//! deployment — API-key digests, PASETO, JWT, client certificates — and this
//! crate does not pick one for you.
//! - **[`HmacRegistry`]** is the scheme in the box: server-issued API keys as
//! HMAC-SHA256 digests, digests at rest, constant-time comparison.
//! - **[`SessionCredential`]** is the part worth centralising whichever scheme
//! you use: in steady state it verifies once per session and compares
//! thereafter, taking the per-request cost to ~16 ns. (Requests racing on a
//! cold session may each verify; that costs the optimisation, never
//! correctness.)
//!
//! The reason that split matters is drift. Getting the cache right means
//! getting an invalidation *ordering* right — clear the old proof before
//! verifying its replacement, never cache a failure, compare the exact bytes
//! that were verified. Left to each embedder, that is a security convention
//! upheld by caller discipline at many call sites, and those drift. Here it is
//! one implementation with its own tests and its own invariant.
//!
//! # A cached credential proves identity, never authorization
//!
//! A cache hit skips the credential check and **nothing else**. The caller
//! still runs admission against the current snapshot, so status, staleness,
//! permissions, rate and quota are decided fresh on every request. Revocation
//! therefore stays bounded by snapshot refresh exactly as it is without any
//! cache — including on a long-lived session that authenticated before the
//! revocation landed.
//!
//! An answer is also bounded by whatever validity the verifier attached to it,
//! so an expiring scheme cannot outlive its own `exp` just because the session
//! stayed open. [`HmacRegistry::install`] preserves each key's `not_after`;
//! convenience `install_credentials` attaches no expiry. A distributed
//! projection can further bound evidence by feed freshness, as the client's
//! `KeyManager` does. Removing a registry entry affects new verification;
//! cached evidence retains its original bound, with snapshot withdrawal still
//! checked on every request.
//!
//! ```
//! use jiff::Timestamp;
//! use tollgate_auth::{HmacRegistry, SessionCredential};
//!
//! let registry = HmacRegistry::new(b"secret-from-the-environment");
//! let issued = registry.install_credentials([b"demo-key-1".as_slice()])[0];
//!
//! // One session — a connection, a TLS session, whatever the transport calls it.
//! let session = SessionCredential::new();
//! // `now` comes from the caller: the request path does not read clocks.
//! let now = Timestamp::from_second(1_755_600_000).unwrap();
//!
//! // First request on it verifies; the rest compare.
//! assert_eq!(session.authenticate(Some(b"demo-key-1"), ®istry, now), Some(issued));
//! assert_eq!(session.authenticate(Some(b"demo-key-1"), ®istry, now), Some(issued));
//!
//! // A different credential re-verifies, and a bad one leaves nothing behind.
//! assert_eq!(session.authenticate(Some(b"wrong"), ®istry, now), None);
//! assert!(!session.is_authenticated());
//! ```
pub use ;
pub use SessionCredential;
pub use ;
// Compiles and runs the README's examples as doctests without adding them to
// the rendered documentation, so the README cannot drift from the API.
;