Skip to main content

veilid_core/protected_store/
native.rs

1use super::*;
2use data_encoding::BASE64URL_NOPAD;
3use keyring_manager::*;
4use std::path::Path;
5
6impl_veilid_log_facility!("pstore");
7
8/// Mutable interior of the `ProtectedStore`, holding the open keyring backend.
9pub struct ProtectedStoreInner {
10    keyring_manager: Option<KeyringManager>,
11}
12impl fmt::Debug for ProtectedStoreInner {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        f.debug_struct("ProtectedStoreInner").finish()
15    }
16}
17
18/// Secure key-value storage for user secrets, backed by the platform's secure keyring
19/// when available and falling back to an on-disk insecure keyring when permitted by config.
20#[derive(Debug)]
21#[must_use]
22pub struct ProtectedStore {
23    registry: VeilidComponentRegistry,
24    inner: Mutex<ProtectedStoreInner>,
25}
26
27impl_veilid_component!(ProtectedStore);
28
29impl ProtectedStore {
30    fn new_inner() -> ProtectedStoreInner {
31        ProtectedStoreInner {
32            keyring_manager: None,
33        }
34    }
35
36    pub(crate) fn new(registry: VeilidComponentRegistry) -> Self {
37        Self {
38            registry,
39            inner: Mutex::new(Self::new_inner()),
40        }
41    }
42
43    #[cfg_attr(feature = "instrument", instrument(level = "trace", skip(self), fields(__VEILID_LOG_KEY = self.log_key())))]
44    /// Remove every known Veilid-managed protected store key, logging any failures.
45    ///
46    /// Idempotent: keys already absent are skipped. Blocks on the keyring backend (OS keyring or disk) once per key.
47    pub fn delete_all(&self) {
48        for kpsk in &KNOWN_PROTECTED_STORE_KEYS {
49            if let Err(e) = self.remove_user_secret(kpsk) {
50                veilid_log!(self error "failed to delete '{}': {}", kpsk, e);
51            } else {
52                veilid_log!(self debug "deleted protected store key '{}'", kpsk);
53            }
54        }
55    }
56
57    fn log_facilities_impl(&self) -> VeilidComponentLogFacilities {
58        VeilidComponentLogFacilities::new().with_facility(
59            VeilidComponentLogFacility::try_new_with_tags("pstore", ["#common"]).unwrap(),
60        )
61    }
62
63    #[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), err, fields(__VEILID_LOG_KEY = self.log_key())))]
64    #[allow(clippy::unused_async)]
65    async fn init_async(&self) -> EyreResult<()> {
66        let delete = {
67            let config = self.config();
68            let mut inner = self.inner.lock();
69            if !config.protected_store.always_use_insecure_storage {
70                // Attempt to open the secure keyring
71                cfg_if! {
72                    if #[cfg(target_os = "android")] {
73                        let maybe_km = KeyringManager::new_secure(&config.program_name, crate::veilid_api::android::get_android_globals());
74                    } else {
75                        let maybe_km = KeyringManager::new_secure(&config.program_name);
76                    }
77                }
78
79                inner.keyring_manager = match maybe_km {
80                    Ok(v) => Some(v),
81                    Err(e) => {
82                        veilid_log!(self info "Secure key storage service unavailable, falling back to direct disk-based storage: {}", e);
83                        None
84                    }
85                };
86            }
87            if (config.protected_store.always_use_insecure_storage
88                || config.protected_store.allow_insecure_fallback)
89                && inner.keyring_manager.is_none()
90            {
91                let directory = Path::new(&config.protected_store.directory);
92                let insecure_keyring_file = directory.to_owned().join(format!(
93                    "insecure_keyring{}",
94                    if config.namespace.is_empty() {
95                        "".to_owned()
96                    } else {
97                        format!("_{}", config.namespace)
98                    }
99                ));
100
101                // Ensure permissions are correct
102                ensure_file_private_owner(&insecure_keyring_file).map_err(|e| eyre!("{}", e))?;
103
104                // Open the insecure keyring
105                inner.keyring_manager = Some(
106                    KeyringManager::new_insecure(&config.program_name, &insecure_keyring_file)
107                        .wrap_err("failed to create insecure keyring")?,
108                );
109            }
110            if inner.keyring_manager.is_none() {
111                bail!("Could not initialize the protected store.");
112            }
113            config.protected_store.delete
114        };
115
116        if delete {
117            self.delete_all();
118        }
119
120        Ok(())
121    }
122
123    #[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), err, fields(__VEILID_LOG_KEY = self.log_key())))]
124    #[allow(clippy::unused_async)]
125    async fn post_init_async(&self) -> EyreResult<()> {
126        Ok(())
127    }
128
129    #[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(__VEILID_LOG_KEY = self.log_key())))]
130    #[allow(clippy::unused_async)]
131    async fn pre_terminate_async(&self) {}
132
133    #[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(__VEILID_LOG_KEY = self.log_key())))]
134    #[allow(clippy::unused_async)]
135    async fn terminate_async(&self) {
136        *self.inner.lock() = Self::new_inner();
137    }
138
139    fn service_name(&self) -> String {
140        let config = self.config();
141        if config.namespace.is_empty() {
142            "veilid_protected_store".to_owned()
143        } else {
144            format!("veilid_protected_store_{}", config.namespace)
145        }
146    }
147
148    #[cfg_attr(
149        feature = "instrument",
150        instrument(level = "trace", skip(self, value), ret, err, fields(__VEILID_LOG_KEY = self.log_key()))
151    )]
152    /// Store a string secret under a key. Returns true if a value already existed for that key.
153    ///
154    /// Overwrites any prior value, so re-saving the same key/value is idempotent. Holds the inner lock and blocks on the keyring backend (OS keyring or disk).
155    ///
156    /// Errors with [VeilidAPIError::NotInitialized] if the store has no open keyring, or [VeilidAPIError::Generic] if the keyring backend rejects the write.
157    pub fn save_user_secret_string<K: AsRef<str> + fmt::Debug, V: AsRef<str> + fmt::Debug>(
158        &self,
159        key: K,
160        value: V,
161    ) -> VeilidAPIResult<bool> {
162        let inner = self.inner.lock();
163        inner
164            .keyring_manager
165            .as_ref()
166            .ok_or_else(VeilidAPIError::not_initialized)?
167            .with_keyring(&self.service_name(), key.as_ref(), |kr| {
168                let existed = kr.get_value().is_ok();
169                kr.set_value(value.as_ref())?;
170                Ok(existed)
171            })
172            .map_err(|e| VeilidAPIError::generic(format!("failed to save user secret: {}", e)))
173    }
174
175    #[cfg_attr(feature = "instrument", instrument(level = "trace", skip(self), err, fields(__VEILID_LOG_KEY = self.log_key())))]
176    /// Load a string secret by key, or `None` if no value is stored for it.
177    ///
178    /// Holds the inner lock and blocks on the keyring backend (OS keyring or disk).
179    ///
180    /// A missing key returns `Ok(None)`. Errors with [VeilidAPIError::NotInitialized] if the store has no open keyring, or [VeilidAPIError::Generic] if the keyring backend fails the read.
181    pub fn load_user_secret_string<K: AsRef<str> + fmt::Debug>(
182        &self,
183        key: K,
184    ) -> VeilidAPIResult<Option<String>> {
185        let inner = self.inner.lock();
186        match inner
187            .keyring_manager
188            .as_ref()
189            .ok_or_else(VeilidAPIError::not_initialized)?
190            .with_keyring(&self.service_name(), key.as_ref(), |kr| kr.get_value())
191        {
192            Ok(v) => Ok(Some(v)),
193            Err(KeyringError::NoPasswordFound) => Ok(None),
194            Err(e) => Err(VeilidAPIError::generic(format!(
195                "Failed to load user secret: {}",
196                e
197            ))),
198        }
199    }
200
201    #[cfg_attr(feature = "instrument", instrument(level = "trace", skip(self, value), fields(__VEILID_LOG_KEY = self.log_key())))]
202    /// Serialize a value to JSON and store it as a secret. Returns true if a value already existed for that key.
203    ///
204    /// Overwrites any prior value. Blocks on the keyring backend (OS keyring or disk).
205    ///
206    /// Errors with [VeilidAPIError::Generic] if `value` fails to serialize, if the keyring backend rejects the write, or [VeilidAPIError::NotInitialized] if the store has no open keyring.
207    pub fn save_user_secret_json<K, T>(&self, key: K, value: &T) -> VeilidAPIResult<bool>
208    where
209        K: AsRef<str> + fmt::Debug,
210        T: serde::Serialize,
211    {
212        let v = serde_json::to_vec(value).map_err(VeilidAPIError::generic)?;
213        self.save_user_secret(&key, &v)
214    }
215
216    #[cfg_attr(feature = "instrument", instrument(level = "trace", skip(self), fields(__VEILID_LOG_KEY = self.log_key())))]
217    /// Load a secret by key and deserialize it from JSON, or `None` if no value is stored for it.
218    ///
219    /// Blocks on the keyring backend (OS keyring or disk).
220    ///
221    /// A missing key returns `Ok(None)`. Errors with [VeilidAPIError::Generic] if the stored bytes fail to deserialize or are not a valid buffer, or [VeilidAPIError::NotInitialized] if the store has no open keyring.
222    pub fn load_user_secret_json<K, T>(&self, key: K) -> VeilidAPIResult<Option<T>>
223    where
224        K: AsRef<str> + fmt::Debug,
225        T: for<'de> serde::de::Deserialize<'de>,
226    {
227        let out = self.load_user_secret(key)?;
228        let b = match out {
229            Some(v) => v,
230            None => {
231                return Ok(None);
232            }
233        };
234
235        let obj = serde_json::from_slice(&b).map_err(VeilidAPIError::generic)?;
236        Ok(Some(obj))
237    }
238
239    #[cfg_attr(
240        feature = "instrument",
241        instrument(level = "trace", skip(self, value), ret, err, fields(__VEILID_LOG_KEY = self.log_key()))
242    )]
243    /// Store a byte buffer as a secret. Returns true if a value already existed for that key.
244    ///
245    /// Overwrites any prior value. Blocks on the keyring backend (OS keyring or disk).
246    ///
247    /// Errors with [VeilidAPIError::NotInitialized] if the store has no open keyring, or [VeilidAPIError::Generic] if the keyring backend rejects the write.
248    pub fn save_user_secret<K: AsRef<str> + fmt::Debug>(
249        &self,
250        key: K,
251        value: &[u8],
252    ) -> VeilidAPIResult<bool> {
253        let mut s = BASE64URL_NOPAD.encode(value);
254        s.push('!');
255
256        self.save_user_secret_string(key, s.as_str())
257    }
258
259    #[cfg_attr(feature = "instrument", instrument(level = "trace", skip(self), err, fields(__VEILID_LOG_KEY = self.log_key())))]
260    /// Load a byte buffer secret by key, or `None` if no value is stored for it.
261    ///
262    /// Blocks on the keyring backend (OS keyring or disk).
263    ///
264    /// A missing key returns `Ok(None)`. Errors with [VeilidAPIError::Generic] if the stored value lacks the buffer marker or fails base64 decode, or [VeilidAPIError::NotInitialized] if the store has no open keyring.
265    pub fn load_user_secret<K: AsRef<str> + fmt::Debug>(
266        &self,
267        key: K,
268    ) -> VeilidAPIResult<Option<Vec<u8>>> {
269        let mut s = match self.load_user_secret_string(key)? {
270            Some(s) => s,
271            None => {
272                return Ok(None);
273            }
274        };
275
276        if s.pop() != Some('!') {
277            apibail_generic!("User secret is not a buffer");
278        }
279
280        let mut bytes = Vec::<u8>::new();
281        let res = BASE64URL_NOPAD.decode_len(s.len());
282        match res {
283            Ok(l) => {
284                bytes.resize(l, 0u8);
285            }
286            Err(_) => {
287                apibail_generic!("Failed to decode");
288            }
289        }
290
291        let res = BASE64URL_NOPAD.decode_mut(s.as_bytes(), &mut bytes);
292        match res {
293            Ok(_) => Ok(Some(bytes)),
294            Err(_) => apibail_generic!("Failed to decode"),
295        }
296    }
297
298    #[cfg_attr(
299        feature = "instrument",
300        instrument(level = "trace", skip(self), ret, err, fields(__VEILID_LOG_KEY = self.log_key()))
301    )]
302    /// Remove a secret by key. Returns true if a value was present and deleted.
303    ///
304    /// No-op returning false when the key is absent, so repeated removes are idempotent. Holds the inner lock and blocks on the keyring backend (OS keyring or disk).
305    ///
306    /// An absent key returns `Ok(false)`. Errors with [VeilidAPIError::NotInitialized] if the store has no open keyring, or [VeilidAPIError::Generic] if the keyring backend fails the delete.
307    pub fn remove_user_secret<K: AsRef<str> + fmt::Debug>(&self, key: K) -> VeilidAPIResult<bool> {
308        let inner = self.inner.lock();
309        match inner
310            .keyring_manager
311            .as_ref()
312            .ok_or_else(VeilidAPIError::not_initialized)?
313            .with_keyring(&self.service_name(), key.as_ref(), |kr| kr.delete_value())
314        {
315            Ok(_) => Ok(true),
316            Err(KeyringError::NoPasswordFound) => Ok(false),
317            Err(e) => Err(VeilidAPIError::generic(format!(
318                "Failed to remove user secret: {}",
319                e
320            ))),
321        }
322    }
323}