Skip to main content

alibi_core/config/
secrets.rs

1use crate::AuthError;
2use indexmap::IndexMap;
3
4/// Versioned persistence keys. New ciphertext uses the current version;
5/// retained versions read existing ciphertext without rewriting it. Removing
6/// a retained version retires it immediately. Signed cookies use only the
7/// current key and are invalidated when that key changes.
8#[derive(Clone)]
9pub struct ManagedSecrets {
10    current_version: u64,
11    keys: IndexMap<u64, String>,
12}
13
14impl ManagedSecrets {
15    #[must_use]
16    pub fn new(version: u64, secret: impl Into<String>) -> Self {
17        Self {
18            current_version: version,
19            keys: IndexMap::from([(version, secret.into())]),
20        }
21    }
22
23    /// Retain a previous version for reads. Replacing the current version's
24    /// key changes its key material; it does not change the selected version.
25    #[must_use]
26    pub fn retain(mut self, version: u64, secret: impl Into<String>) -> Self {
27        _ = self.keys.insert(version, secret.into());
28        self
29    }
30
31    /// Retire a reader. The current version cannot be retired; rotate by
32    /// constructing a new configuration with a new current version instead.
33    #[must_use]
34    pub fn retire(mut self, version: u64) -> Self {
35        if version != self.current_version {
36            _ = self.keys.shift_remove(&version);
37        }
38        self
39    }
40
41    #[must_use]
42    pub const fn current_version(&self) -> u64 {
43        self.current_version
44    }
45
46    #[must_use]
47    pub fn current_secret(&self) -> &str {
48        self.key(self.current_version).unwrap_or_default()
49    }
50
51    #[must_use]
52    pub fn key(&self, version: u64) -> Option<&str> {
53        self.keys.get(&version).map(String::as_str)
54    }
55
56    pub fn verification_secrets(&self) -> impl Iterator<Item = &str> {
57        self.keys.values().map(String::as_str)
58    }
59
60    /// # Errors
61    /// Rejects invalid versions, empty readers and an insecure current key.
62    pub fn validate(&self) -> Result<(), AuthError> {
63        if self
64            .keys
65            .iter()
66            .any(|(version, key)| *version > 9_007_199_254_740_991 || key.is_empty())
67        {
68            return Err(AuthError::config(
69                "Secret versions must be safe nonnegative integers with nonempty keys",
70            ));
71        }
72        if self.current_secret().len() < 32 {
73            return Err(AuthError::config(
74                "Current secret key must be at least 32 characters",
75            ));
76        }
77        Ok(())
78    }
79}
80
81impl std::fmt::Debug for ManagedSecrets {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        f.debug_struct("ManagedSecrets")
84            .field("current_version", &self.current_version)
85            .field("versions", &self.keys.keys())
86            .finish_non_exhaustive()
87    }
88}