krabka_audit/operator_keys/fingerprint.rs
1//! The fingerprint of a configured approver set.
2//!
3//! Every break-glass audit event carries this hash, so an auditor sees which
4//! approver set a broker held when it made a decision. The hash covers the
5//! members alone, and not the order an operator wrote them in.
6
7use {
8 sha2::{Digest as _, Sha256},
9 std::collections::BTreeSet,
10};
11
12/// SHA-256 hex fingerprint of a configured approver set.
13///
14/// The approver set comes from each broker's own `broker.toml`, so two brokers
15/// can legitimately disagree during a rolling config change. Every break-glass
16/// audit event records this fingerprint, which makes the disagreement visible
17/// after the fact.
18///
19/// The input is sorted and de-duplicated first, and each name is
20/// length-prefixed, so the fingerprint depends on the members alone: not on the
21/// order an operator wrote them in, and not on where one name ends and the next
22/// begins.
23#[must_use]
24pub fn approver_set_fingerprint(approvers: &[String]) -> String {
25 let unique: BTreeSet<&str> = approvers.iter().map(String::as_str).collect();
26 let mut hasher = Sha256::new();
27 for approver in unique {
28 let len = u32::try_from(approver.len()).unwrap_or(u32::MAX);
29 hasher.update(len.to_be_bytes());
30 hasher.update(approver.as_bytes());
31 }
32 hex::encode(hasher.finalize())
33}