Skip to main content

galata_vault/
store.rs

1//! Key storage, supplied by the caller.
2//!
3//! The owner API reads and writes node keys only through a [`KeyStore`].
4//! This crate ships no implementation that touches the operating system:
5//! `gv` implements it over the OS keychain and a 0600 credentials file, and a
6//! product brings its own secure storage. Only held keys are stored (a
7//! project key, or a node key imported from a kit, and a rekey's keys while
8//! it runs); keys derived below a held one are recomputed on every use.
9
10use std::sync::Arc;
11
12use crate::proto::ids::{TokenId, VaultId};
13use crate::proto::path::EnvPath;
14use zeroize::Zeroizing;
15
16/// A key store or state store failed. The message names what, never the
17/// key.
18#[derive(Debug, Clone)]
19pub struct StoreError {
20    message: String,
21    source: Option<Arc<dyn std::error::Error + Send + Sync>>,
22}
23
24impl StoreError {
25    /// A failure described by `message`.
26    pub fn new(message: impl Into<String>) -> StoreError {
27        StoreError {
28            message: message.into(),
29            source: None,
30        }
31    }
32
33    /// A failure described by `message`, caused by `source`.
34    pub fn with_source(
35        message: impl Into<String>,
36        source: impl std::error::Error + Send + Sync + 'static,
37    ) -> StoreError {
38        StoreError {
39            message: message.into(),
40            source: Some(Arc::new(source)),
41        }
42    }
43
44    /// The message.
45    pub fn message(&self) -> &str {
46        &self.message
47    }
48}
49
50impl std::fmt::Display for StoreError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.write_str(&self.message)
53    }
54}
55
56impl std::error::Error for StoreError {
57    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
58        self.source
59            .as_deref()
60            .map(|e| e as &(dyn std::error::Error + 'static))
61    }
62}
63
64/// Which key an entry holds.
65#[derive(Debug, Clone, PartialEq, Eq)]
66#[non_exhaustive]
67pub enum KeyName {
68    /// A held node key: a project root, or a node imported from a kit.
69    Node(EnvPath),
70    /// A token string, for a caller that keeps tokens beside its keys.
71    Token {
72        /// The token's vault.
73        vault: VaultId,
74        /// The token's id.
75        id: TokenId,
76    },
77    /// A rekey's old root key, kept until the rekey completes.
78    RekeyOld(EnvPath),
79    /// A rekey's new root key, kept until the rekey completes.
80    RekeyNew(EnvPath),
81}
82
83impl KeyName {
84    /// A stable string for the entry, as `gv` names its keychain entries:
85    /// `node:acme/dev`, `rekey-old:acme`, `rekey-new:acme`,
86    /// `token:<vault hex>:<id hex>`.
87    pub fn entry(&self) -> String {
88        match self {
89            KeyName::Node(p) => format!("node:{p}"),
90            KeyName::Token { vault, id } => format!("token:{}:{}", vault.to_hex(), id.to_hex()),
91            KeyName::RekeyOld(p) => format!("rekey-old:{p}"),
92            KeyName::RekeyNew(p) => format!("rekey-new:{p}"),
93        }
94    }
95}
96
97impl std::fmt::Display for KeyName {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.write_str(&self.entry())
100    }
101}
102
103/// Where node keys live. Values cross this trait in zeroizing buffers.
104pub trait KeyStore: Send + Sync {
105    /// The value stored for `key`, or `None`.
106    fn get(&self, key: &KeyName) -> Result<Option<Zeroizing<String>>, StoreError>;
107    /// Store `value` for `key`, replacing any.
108    fn set(&self, key: &KeyName, value: &str) -> Result<(), StoreError>;
109    /// Forget `key`. Forgetting an absent key succeeds.
110    fn delete(&self, key: &KeyName) -> Result<(), StoreError>;
111}
112
113impl<K: KeyStore + ?Sized> KeyStore for Arc<K> {
114    fn get(&self, key: &KeyName) -> Result<Option<Zeroizing<String>>, StoreError> {
115        (**self).get(key)
116    }
117
118    fn set(&self, key: &KeyName, value: &str) -> Result<(), StoreError> {
119        (**self).set(key, value)
120    }
121
122    fn delete(&self, key: &KeyName) -> Result<(), StoreError> {
123        (**self).delete(key)
124    }
125}
126
127/// Keys in memory, for tests (feature `test-util`). Cleared on drop.
128#[cfg(feature = "test-util")]
129#[derive(Default)]
130pub struct MemoryKeyStore {
131    entries: std::sync::Mutex<std::collections::BTreeMap<String, Zeroizing<String>>>,
132}
133
134#[cfg(feature = "test-util")]
135impl std::fmt::Debug for MemoryKeyStore {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.debug_struct("MemoryKeyStore")
138            .field("entries", &self.names())
139            .finish()
140    }
141}
142
143#[cfg(feature = "test-util")]
144impl MemoryKeyStore {
145    /// An empty store.
146    pub fn new() -> MemoryKeyStore {
147        MemoryKeyStore::default()
148    }
149
150    /// The entry names held, sorted. Never the values.
151    pub fn names(&self) -> Vec<String> {
152        self.lock().keys().cloned().collect()
153    }
154
155    fn lock(
156        &self,
157    ) -> std::sync::MutexGuard<'_, std::collections::BTreeMap<String, Zeroizing<String>>> {
158        self.entries.lock().unwrap_or_else(|p| p.into_inner())
159    }
160}
161
162#[cfg(feature = "test-util")]
163impl KeyStore for MemoryKeyStore {
164    fn get(&self, key: &KeyName) -> Result<Option<Zeroizing<String>>, StoreError> {
165        Ok(self.lock().get(&key.entry()).cloned())
166    }
167
168    fn set(&self, key: &KeyName, value: &str) -> Result<(), StoreError> {
169        self.lock()
170            .insert(key.entry(), Zeroizing::new(value.to_owned()));
171        Ok(())
172    }
173
174    fn delete(&self, key: &KeyName) -> Result<(), StoreError> {
175        self.lock().remove(&key.entry());
176        Ok(())
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn entry_names_are_the_ones_gv_has_always_used() {
186        let p: EnvPath = "acme/dev".parse().unwrap();
187        assert_eq!(KeyName::Node(p.clone()).entry(), "node:acme/dev");
188        assert_eq!(KeyName::RekeyOld(p.clone()).entry(), "rekey-old:acme/dev");
189        assert_eq!(KeyName::RekeyNew(p).entry(), "rekey-new:acme/dev");
190    }
191}