aries_askar/
store.rs

1use askar_storage::backend::{copy_profile, OrderBy};
2
3use crate::{
4    error::Error,
5    kms::{KeyEntry, KeyParams, KeyReference, KmsCategory, LocalKey},
6    storage::{
7        any::{AnyBackend, AnyBackendSession},
8        backend::{Backend, BackendSession, ManageBackend},
9        entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter},
10        generate_raw_store_key,
11    },
12};
13
14pub use crate::storage::{entry, PassKey, StoreKeyMethod};
15
16#[derive(Debug, Clone)]
17/// An instance of an opened store
18pub struct Store(AnyBackend);
19
20impl Store {
21    pub(crate) fn new(inner: AnyBackend) -> Self {
22        Self(inner)
23    }
24
25    /// Provision a new store instance using a database URL
26    pub async fn provision(
27        db_url: &str,
28        key_method: StoreKeyMethod,
29        pass_key: PassKey<'_>,
30        profile: Option<String>,
31        recreate: bool,
32    ) -> Result<Self, Error> {
33        let backend = db_url
34            .provision_backend(key_method, pass_key, profile, recreate)
35            .await?;
36        Ok(Self::new(backend))
37    }
38
39    /// Open a store instance from a database URL
40    pub async fn open(
41        db_url: &str,
42        key_method: Option<StoreKeyMethod>,
43        pass_key: PassKey<'_>,
44        profile: Option<String>,
45    ) -> Result<Self, Error> {
46        let backend = db_url.open_backend(key_method, pass_key, profile).await?;
47        Ok(Self::new(backend))
48    }
49
50    /// Remove a store instance using a database URL
51    pub async fn remove(db_url: &str) -> Result<bool, Error> {
52        Ok(db_url.remove_backend().await?)
53    }
54
55    /// Generate a new raw store key
56    pub fn new_raw_key(seed: Option<&[u8]>) -> Result<PassKey<'static>, Error> {
57        Ok(generate_raw_store_key(seed)?)
58    }
59
60    /// Get the default profile name used when starting a scan or a session
61    pub fn get_active_profile(&self) -> String {
62        self.0.get_active_profile()
63    }
64
65    /// Get the default profile name used when opening the Store
66    pub async fn get_default_profile(&self) -> Result<String, Error> {
67        Ok(self.0.get_default_profile().await?)
68    }
69
70    /// Set the default profile name used when opening the Store
71    pub async fn set_default_profile(&self, profile: String) -> Result<(), Error> {
72        Ok(self.0.set_default_profile(profile).await?)
73    }
74
75    /// Replace the wrapping key on a store
76    pub async fn rekey(
77        &mut self,
78        method: StoreKeyMethod,
79        pass_key: PassKey<'_>,
80    ) -> Result<(), Error> {
81        Ok(self.0.rekey(method, pass_key).await?)
82    }
83
84    /// Copy to a new store instance using a database URL
85    pub async fn copy_to(
86        &self,
87        target_url: &str,
88        key_method: StoreKeyMethod,
89        pass_key: PassKey<'_>,
90        recreate: bool,
91    ) -> Result<Self, Error> {
92        let default_profile = self.get_default_profile().await?;
93        let profile_ids = self.list_profiles().await?;
94        let target = target_url
95            .provision_backend(key_method, pass_key, Some(default_profile), recreate)
96            .await?;
97        for profile in profile_ids {
98            copy_profile(&self.0, &target, &profile, &profile).await?;
99        }
100        Ok(Self::new(target))
101    }
102
103    /// Copy to a new store instance using a database URL
104    pub async fn copy_profile_to(
105        &self,
106        target: &Store,
107        from_name: &str,
108        to_name: &str,
109    ) -> Result<(), Error> {
110        copy_profile(&self.0, &target.0, from_name, to_name).await?;
111        Ok(())
112    }
113
114    /// Create a new profile with the given profile name
115    pub async fn create_profile(&self, name: Option<String>) -> Result<String, Error> {
116        Ok(self.0.create_profile(name).await?)
117    }
118
119    /// Get the details of all store profiles
120    pub async fn list_profiles(&self) -> Result<Vec<String>, Error> {
121        Ok(self.0.list_profiles().await?)
122    }
123
124    /// Remove an existing profile with the given profile namestore.r
125    pub async fn remove_profile(&self, name: String) -> Result<bool, Error> {
126        Ok(self.0.remove_profile(name).await?)
127    }
128
129    /// Change the name of an existing profile
130    pub async fn rename_profile(
131        &self,
132        from_profile: String,
133        to_profile: String,
134    ) -> Result<bool, Error> {
135        Ok(self.0.rename_profile(from_profile, to_profile).await?)
136    }
137    /// Create a new scan instance against the store
138    ///
139    /// The result will keep an open connection to the backend until it is consumed
140    #[allow(clippy::too_many_arguments)]
141    pub async fn scan(
142        &self,
143        profile: Option<String>,
144        category: Option<String>,
145        tag_filter: Option<TagFilter>,
146        offset: Option<i64>,
147        limit: Option<i64>,
148        order_by: Option<OrderBy>,
149        descending: bool,
150    ) -> Result<Scan<'static, Entry>, Error> {
151        Ok(self
152            .0
153            .scan(
154                profile,
155                Some(EntryKind::Item),
156                category,
157                tag_filter,
158                offset,
159                limit,
160                order_by,
161                descending,
162            )
163            .await?)
164    }
165
166    /// Create a new session against the store
167    pub async fn session(&self, profile: Option<String>) -> Result<Session, Error> {
168        let mut sess = Session::new(self.0.session(profile, false)?);
169        if let Err(e) = sess.ping().await {
170            sess.0.close(false).await?;
171            Err(e)
172        } else {
173            Ok(sess)
174        }
175    }
176
177    /// Create a new transaction session against the store
178    pub async fn transaction(&self, profile: Option<String>) -> Result<Session, Error> {
179        let mut txn = Session::new(self.0.session(profile, true)?);
180        if let Err(e) = txn.ping().await {
181            txn.0.close(false).await?;
182            Err(e)
183        } else {
184            Ok(txn)
185        }
186    }
187
188    /// Close the store instance, waiting for any shutdown procedures to complete.
189    pub async fn close(self) -> Result<(), Error> {
190        Ok(self.0.close().await?)
191    }
192}
193
194impl From<AnyBackend> for Store {
195    fn from(backend: AnyBackend) -> Self {
196        Self::new(backend)
197    }
198}
199
200/// An active connection to the store backend
201#[derive(Debug)]
202pub struct Session(AnyBackendSession);
203
204impl Session {
205    pub(crate) fn new(inner: AnyBackendSession) -> Self {
206        Self(inner)
207    }
208
209    /// Count the number of entries for a given record category
210    pub async fn count(
211        &mut self,
212        category: Option<&str>,
213        tag_filter: Option<TagFilter>,
214    ) -> Result<i64, Error> {
215        Ok(self
216            .0
217            .count(Some(EntryKind::Item), category, tag_filter)
218            .await?)
219    }
220
221    /// Retrieve the current record at `(category, name)`.
222    ///
223    /// Specify `for_update` when in a transaction to create an update lock on the
224    /// associated record, if supported by the store backend
225    pub async fn fetch(
226        &mut self,
227        category: &str,
228        name: &str,
229        for_update: bool,
230    ) -> Result<Option<Entry>, Error> {
231        Ok(self
232            .0
233            .fetch(EntryKind::Item, category, name, for_update)
234            .await?)
235    }
236
237    /// Retrieve all records matching the given `category` and `tag_filter`.
238    ///
239    /// Unlike `Store::scan`, this method may be used within a transaction. It should
240    /// not be used for very large result sets due to correspondingly large memory
241    /// requirements
242    pub async fn fetch_all(
243        &mut self,
244        category: Option<&str>,
245        tag_filter: Option<TagFilter>,
246        limit: Option<i64>,
247        order_by: Option<OrderBy>,
248        descending: bool,
249        for_update: bool,
250    ) -> Result<Vec<Entry>, Error> {
251        Ok(self
252            .0
253            .fetch_all(
254                Some(EntryKind::Item),
255                category,
256                tag_filter,
257                limit,
258                order_by,
259                descending,
260                for_update,
261            )
262            .await?)
263    }
264
265    /// Insert a new record into the store
266    pub async fn insert(
267        &mut self,
268        category: &str,
269        name: &str,
270        value: &[u8],
271        tags: Option<&[EntryTag]>,
272        expiry_ms: Option<i64>,
273    ) -> Result<(), Error> {
274        Ok(self
275            .0
276            .update(
277                EntryKind::Item,
278                EntryOperation::Insert,
279                category,
280                name,
281                Some(value),
282                tags,
283                expiry_ms,
284            )
285            .await?)
286    }
287
288    /// Remove a record from the store
289    pub async fn remove(&mut self, category: &str, name: &str) -> Result<(), Error> {
290        Ok(self
291            .0
292            .update(
293                EntryKind::Item,
294                EntryOperation::Remove,
295                category,
296                name,
297                None,
298                None,
299                None,
300            )
301            .await?)
302    }
303
304    /// Replace the value and tags of a record in the store
305    pub async fn replace(
306        &mut self,
307        category: &str,
308        name: &str,
309        value: &[u8],
310        tags: Option<&[EntryTag]>,
311        expiry_ms: Option<i64>,
312    ) -> Result<(), Error> {
313        Ok(self
314            .0
315            .update(
316                EntryKind::Item,
317                EntryOperation::Replace,
318                category,
319                name,
320                Some(value),
321                tags,
322                expiry_ms,
323            )
324            .await?)
325    }
326
327    /// Remove all records in the store matching a given `category` and `tag_filter`
328    pub async fn remove_all(
329        &mut self,
330        category: Option<&str>,
331        tag_filter: Option<TagFilter>,
332    ) -> Result<i64, Error> {
333        Ok(self
334            .0
335            .remove_all(Some(EntryKind::Item), category, tag_filter)
336            .await?)
337    }
338
339    /// Perform a record update
340    ///
341    /// This may correspond to an record insert, replace, or remove depending on
342    /// the provided `operation`
343    pub async fn update(
344        &mut self,
345        operation: EntryOperation,
346        category: &str,
347        name: &str,
348        value: Option<&[u8]>,
349        tags: Option<&[EntryTag]>,
350        expiry_ms: Option<i64>,
351    ) -> Result<(), Error> {
352        Ok(self
353            .0
354            .update(
355                EntryKind::Item,
356                operation,
357                category,
358                name,
359                value,
360                tags,
361                expiry_ms,
362            )
363            .await?)
364    }
365
366    /// Insert a local key instance into the store
367    pub async fn insert_key(
368        &mut self,
369        name: &str,
370        key: &LocalKey,
371        metadata: Option<&str>,
372        reference: Option<KeyReference>,
373        tags: Option<&[EntryTag]>,
374        expiry_ms: Option<i64>,
375    ) -> Result<(), Error> {
376        let data = if key.is_hardware_backed() {
377            key.inner.key_id()?
378        } else {
379            key.encode()?
380        };
381        let params = KeyParams {
382            metadata: metadata.map(str::to_string),
383            reference,
384            data: Some(data),
385        };
386        let value = params.to_bytes()?;
387        let mut ins_tags = Vec::with_capacity(10);
388        let alg = key.algorithm().as_str();
389        if !alg.is_empty() {
390            ins_tags.push(EntryTag::Encrypted("alg".to_string(), alg.to_string()));
391        }
392        let thumbs = key.to_jwk_thumbprints()?;
393        for thumb in thumbs {
394            ins_tags.push(EntryTag::Encrypted("thumb".to_string(), thumb));
395        }
396        if let Some(tags) = tags {
397            for t in tags {
398                ins_tags.push(t.map_ref(|k, v| (format!("user:{}", k), v.to_string())));
399            }
400        }
401        self.0
402            .update(
403                EntryKind::Kms,
404                EntryOperation::Insert,
405                KmsCategory::CryptoKey.as_str(),
406                name,
407                Some(value.as_ref()),
408                Some(ins_tags.as_slice()),
409                expiry_ms,
410            )
411            .await?;
412        Ok(())
413    }
414
415    /// Fetch an existing key from the store
416    ///
417    /// Specify `for_update` when in a transaction to create an update lock on the
418    /// associated record, if supported by the store backend
419    pub async fn fetch_key(
420        &mut self,
421        name: &str,
422        for_update: bool,
423    ) -> Result<Option<KeyEntry>, Error> {
424        Ok(
425            if let Some(row) = self
426                .0
427                .fetch(
428                    EntryKind::Kms,
429                    KmsCategory::CryptoKey.as_str(),
430                    name,
431                    for_update,
432                )
433                .await?
434            {
435                Some(KeyEntry::from_entry(row)?)
436            } else {
437                None
438            },
439        )
440    }
441
442    /// Retrieve all keys matching the given filters.
443    pub async fn fetch_all_keys(
444        &mut self,
445        algorithm: Option<&str>,
446        thumbprint: Option<&str>,
447        tag_filter: Option<TagFilter>,
448        limit: Option<i64>,
449        for_update: bool,
450    ) -> Result<Vec<KeyEntry>, Error> {
451        let mut query_parts = Vec::with_capacity(3);
452        if let Some(query) = tag_filter.map(|f| f.into_query()) {
453            query_parts.push(TagFilter::from(
454                query
455                    .map_names(|mut k| {
456                        k.replace_range(0..0, "user:");
457                        Result::<_, ()>::Ok(k)
458                    })
459                    .unwrap(),
460            ));
461        }
462        if let Some(algorithm) = algorithm {
463            query_parts.push(TagFilter::is_eq("alg", algorithm));
464        }
465        if let Some(thumbprint) = thumbprint {
466            query_parts.push(TagFilter::is_eq("thumb", thumbprint));
467        }
468        let tag_filter = if query_parts.is_empty() {
469            None
470        } else {
471            Some(TagFilter::all_of(query_parts))
472        };
473        let rows = self
474            .0
475            .fetch_all(
476                Some(EntryKind::Kms),
477                Some(KmsCategory::CryptoKey.as_str()),
478                tag_filter,
479                limit,
480                None,
481                false,
482                for_update,
483            )
484            .await?;
485        let mut entries = Vec::with_capacity(rows.len());
486        for row in rows {
487            entries.push(KeyEntry::from_entry(row)?)
488        }
489        Ok(entries)
490    }
491
492    /// Remove an existing key from the store
493    pub async fn remove_key(&mut self, name: &str) -> Result<(), Error> {
494        Ok(self
495            .0
496            .update(
497                EntryKind::Kms,
498                EntryOperation::Remove,
499                KmsCategory::CryptoKey.as_str(),
500                name,
501                None,
502                None,
503                None,
504            )
505            .await?)
506    }
507
508    /// Replace the metadata and tags on an existing key in the store
509    pub async fn update_key(
510        &mut self,
511        name: &str,
512        metadata: Option<&str>,
513        tags: Option<&[EntryTag]>,
514        expiry_ms: Option<i64>,
515    ) -> Result<(), Error> {
516        let row = self
517            .0
518            .fetch(EntryKind::Kms, KmsCategory::CryptoKey.as_str(), name, true)
519            .await?
520            .ok_or_else(|| err_msg!(NotFound, "Key entry not found"))?;
521
522        let mut params = KeyParams::from_slice(&row.value)?;
523        params.metadata = metadata.map(str::to_string);
524        let value = params.to_bytes()?;
525
526        let mut upd_tags = Vec::with_capacity(10);
527        if let Some(tags) = tags {
528            for t in tags {
529                upd_tags.push(t.map_ref(|k, v| (format!("user:{}", k), v.to_string())));
530            }
531        }
532        for t in row.tags {
533            if !t.name().starts_with("user:") {
534                upd_tags.push(t);
535            }
536        }
537
538        self.0
539            .update(
540                EntryKind::Kms,
541                EntryOperation::Replace,
542                KmsCategory::CryptoKey.as_str(),
543                name,
544                Some(value.as_ref()),
545                Some(upd_tags.as_slice()),
546                expiry_ms,
547            )
548            .await?;
549
550        Ok(())
551    }
552
553    /// Test the connection to the store
554    pub async fn ping(&mut self) -> Result<(), Error> {
555        Ok(self.0.ping().await?)
556    }
557
558    /// Commit the pending transaction
559    pub async fn commit(mut self) -> Result<(), Error> {
560        Ok(self.0.close(true).await?)
561    }
562
563    /// Roll back the pending transaction
564    pub async fn rollback(mut self) -> Result<(), Error> {
565        Ok(self.0.close(false).await?)
566    }
567}