Skip to main content

krabka_audit/operator_keys/
trust_set.rs

1//! The loaded trust set: the keys it holds, and the check a signature passes.
2//!
3//! [`OperatorKeys::load`] validates every entry before it keeps one, so a set
4//! that loads holds one key per `key_id` and one key per principal. A lookup
5//! is by `key_id` alone. [`OperatorKeys::verify`] adds the principal binding
6//! to the signature check, so one operator's key never signs in another's
7//! name.
8
9use {
10    super::{OperatorKeyEntry, OperatorKeyError, key_file::read_public_key},
11    crate::signing::verify_signature,
12    std::collections::BTreeMap,
13};
14
15/// One loaded operator key and the principal it is bound to.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct OperatorKey {
18    key_id: String,
19    principal: String,
20    public_key: Vec<u8>,
21}
22
23impl OperatorKey {
24    /// Stable identifier that a signed record names.
25    #[must_use]
26    pub fn key_id(&self) -> &str { &self.key_id }
27
28    /// The principal this key speaks for.
29    ///
30    /// A record that claims another author is refused even when its signature
31    /// verifies, so one operator's key cannot sign in another's name.
32    #[must_use]
33    pub fn principal(&self) -> &str { &self.principal }
34
35    /// The raw 32-byte Ed25519 public key.
36    #[must_use]
37    pub fn public_key(&self) -> &[u8] { &self.public_key }
38}
39
40/// The loaded, validated operator key trust set.
41///
42/// An empty set is the default and means no operator key is provisioned. The
43/// file-config layer refuses a configuration that demands a signature against
44/// an empty set.
45#[derive(Debug, Clone, Default, PartialEq, Eq)]
46pub struct OperatorKeys {
47    by_id: BTreeMap<String, OperatorKey>,
48}
49
50impl OperatorKeys {
51    /// Read every configured public key file and build the trust set.
52    ///
53    /// # Errors
54    ///
55    /// Returns [`OperatorKeyError`] when an entry has a blank `key_id` or
56    /// `principal`, when its `public_key_path` cannot be read, when the file
57    /// does not hold a 32-byte Ed25519 public key, or when two entries share a
58    /// `key_id` or a `principal`.
59    pub fn load(entries: &[OperatorKeyEntry]) -> Result<Self, OperatorKeyError> {
60        let mut by_id: BTreeMap<String, OperatorKey> = BTreeMap::new();
61        let mut by_principal: BTreeMap<String, String> = BTreeMap::new();
62        for (index, entry) in entries.iter().enumerate() {
63            if entry.key_id.trim().is_empty() {
64                return Err(OperatorKeyError::BlankField {
65                    index,
66                    field: "key_id",
67                });
68            }
69            if entry.principal.trim().is_empty() {
70                return Err(OperatorKeyError::BlankField {
71                    index,
72                    field: "principal",
73                });
74            }
75            if by_id.contains_key(&entry.key_id) {
76                return Err(OperatorKeyError::DuplicateKeyId {
77                    key_id: entry.key_id.clone(),
78                });
79            }
80            if let Some(first) = by_principal.get(&entry.principal) {
81                return Err(OperatorKeyError::DuplicatePrincipal {
82                    principal: entry.principal.clone(),
83                    first: first.clone(),
84                    second: entry.key_id.clone(),
85                });
86            }
87            let public_key = read_public_key(&entry.key_id, &entry.public_key_path)?;
88            by_principal.insert(entry.principal.clone(), entry.key_id.clone());
89            by_id.insert(entry.key_id.clone(), OperatorKey {
90                key_id: entry.key_id.clone(),
91                principal: entry.principal.clone(),
92                public_key,
93            });
94        }
95        Ok(Self { by_id })
96    }
97
98    /// The key registered under `key_id`, with the principal it is bound to.
99    #[must_use]
100    pub fn get(&self, key_id: &str) -> Option<&OperatorKey> { self.by_id.get(key_id) }
101
102    /// How many keys the trust set holds.
103    #[must_use]
104    pub fn len(&self) -> usize { self.by_id.len() }
105
106    /// Whether no operator key is provisioned.
107    #[must_use]
108    pub fn is_empty(&self) -> bool { self.by_id.is_empty() }
109
110    /// Verify `signature` over `message` as made by `key_id` on behalf of
111    /// `principal`.
112    ///
113    /// The principal binding is part of the check: a signature made by Alice's
114    /// key over a record that claims Bob wrote it does not verify. The caller
115    /// supplies the canonical bytes of its own signed payload, under its own
116    /// domain separator.
117    #[must_use]
118    pub fn verify(&self, key_id: &str, principal: &str, message: &[u8], signature: &[u8]) -> bool {
119        self.get(key_id).is_some_and(|key| {
120            key.principal == principal && verify_signature(&key.public_key, message, signature)
121        })
122    }
123}