Skip to main content

agentmux_common/
secret_eq.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Constant-time comparison for secrets presented by a caller.
5//!
6//! `==` on strings returns at the first differing byte, so how long a
7//! rejection takes tells a caller how many leading bytes of its guess were
8//! right. Every check of a caller-supplied secret against the real one (srv's
9//! auth key and LAN key, the CEF host's IPC token, a re-registering host's
10//! token, a webhook verify token) goes through [`secret_eq`] instead.
11//!
12//! The length is not hidden: a length mismatch returns early. Every secret
13//! compared here has a fixed, public length (UUIDs, fixed-size tokens), so
14//! the length reveals nothing.
15
16/// True when `supplied` equals `expected`, in time that depends only on the
17/// length, never on where the first difference is.
18pub fn secret_eq(supplied: &[u8], expected: &[u8]) -> bool {
19    if supplied.len() != expected.len() {
20        return false;
21    }
22    let mut diff: u8 = 0;
23    for (x, y) in supplied.iter().zip(expected.iter()) {
24        diff |= x ^ y;
25    }
26    // Keep the optimizer from turning the loop back into an early exit.
27    std::hint::black_box(diff) == 0
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn equal_secrets_match() {
36        assert!(secret_eq(b"3f2b9c1e-auth-key", b"3f2b9c1e-auth-key"));
37    }
38
39    #[test]
40    fn same_length_different_secrets_do_not_match() {
41        assert!(!secret_eq(b"3f2b9c1e-auth-key", b"3f2b9c1e-auth-kez"));
42        assert!(!secret_eq(b"Xf2b9c1e-auth-key", b"3f2b9c1e-auth-key"));
43    }
44
45    #[test]
46    fn different_lengths_do_not_match() {
47        assert!(!secret_eq(b"short", b"a-much-longer-secret"));
48        assert!(!secret_eq(b"3f2b9c1e-auth-key", b"3f2b9c1e-auth-key-"));
49    }
50
51    #[test]
52    fn empty_matches_only_empty() {
53        assert!(secret_eq(b"", b""));
54        assert!(!secret_eq(b"", b"x"));
55    }
56}