Skip to main content

max / makenotwork

1.6 KB · 44 lines History Blame Raw
1 //! Shared HTTP-daemon helpers for the operator tools (Sando, Bento).
2 //!
3 //! Both daemons expose a small axum service guarded by a single bearer token,
4 //! and both compared that token the same way — a constant-time byte compare, to
5 //! the byte, test included. That one belongs here so there is exactly one copy.
6 //!
7 //! Not everything the two daemons appear to share actually is: `require_bearer`
8 //! diverged (Sando attributes each authenticated POST to its peer; Bento leaves
9 //! reads open and logs nothing), and the `Error` → response mappers carry
10 //! different variants and leak postures (Bento answers a leak-safe JSON
11 //! envelope; Sando returns `to_string()` text). Folding those is a behavior
12 //! change, not a mechanical dedup, so each daemon keeps its own — only the
13 //! genuinely identical primitive lives here.
14
15 /// Constant-time string compare for bearer tokens. The length may leak (a
16 /// token's length is not the secret); the byte comparison does not
17 /// short-circuit, so a matching prefix is not distinguishable by timing.
18 #[must_use]
19 pub fn ct_eq(a: &str, b: &str) -> bool {
20 let (a, b) = (a.as_bytes(), b.as_bytes());
21 if a.len() != b.len() {
22 return false;
23 }
24 let mut diff = 0u8;
25 for (x, y) in a.iter().zip(b) {
26 diff |= x ^ y;
27 }
28 diff == 0
29 }
30
31 #[cfg(test)]
32 mod tests {
33 use super::*;
34
35 #[test]
36 fn ct_eq_matches_only_identical_strings() {
37 assert!(ct_eq("abc", "abc"));
38 assert!(!ct_eq("abc", "abd"));
39 assert!(!ct_eq("abc", "ab"));
40 assert!(!ct_eq("", "x"));
41 assert!(ct_eq("", ""));
42 }
43 }
44