engine/vault/
view.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::vault::{
5    crypto_box::{BoxProvider, Decrypt, Encrypt, Key},
6    types::{
7        transactions::{DataTransaction, RevocationTransaction, SealedBlob, SealedTransaction},
8        utils::{BlobId, ChainId, RecordHint, RecordId, VaultId},
9    },
10};
11
12use runtime::memories::buffer::Buffer;
13use serde::{Deserialize, Serialize};
14use std::{collections::HashMap, convert::Infallible, fmt::Debug};
15use thiserror::Error as DeriveError;
16use zeroize::Zeroizing;
17
18use super::{crypto_box::DecryptError, types::transactions::Transaction};
19
20#[derive(DeriveError, Debug)]
21pub enum VaultError<TProvErr: Debug, TProcErr: Debug = Infallible> {
22    #[error("vault `{0:?}` does not exist")]
23    VaultNotFound(VaultId),
24
25    #[error("record error: `{0:?}`")]
26    Record(#[from] RecordError<TProvErr>),
27
28    #[error("procedure error `{0:?}`")]
29    Procedure(TProcErr),
30
31    #[error("Lock is poisoned")]
32    LockPoisoned,
33}
34
35#[derive(DeriveError, Debug)]
36pub enum RecordError<TProvErr: Debug> {
37    #[error("provider error: `{0:?}`")]
38    Provider(TProvErr),
39
40    #[error("decrypted content does not match expected format: {0}")]
41    CorruptedContent(String),
42
43    #[error("invalid key provided")]
44    InvalidKey,
45
46    #[error("no record with `{0:?}`")]
47    RecordNotFound(ChainId),
48
49    #[error("lock is poisoned")]
50    LockPoisoned,
51}
52
53/// A view over the data inside of a collection of [`Vault`] types.
54#[derive(Deserialize, Serialize, Clone, Default)]
55pub struct DbView<P: BoxProvider> {
56    /// A hashmap of the [`Vault`] types.
57    pub vaults: HashMap<VaultId, Vault<P>>,
58}
59
60/// A enclave of data that is encrypted under one [`Key`].
61#[derive(Deserialize, Serialize, Clone)]
62pub struct Vault<P: BoxProvider> {
63    key: Key<P>,
64    entries: HashMap<ChainId, Record>,
65}
66
67/// A bit of data inside of a [`Vault`].
68#[derive(Deserialize, Serialize, Clone)]
69pub struct Record {
70    /// record id.
71    id: ChainId,
72    /// data transaction metadata.
73    data: SealedTransaction,
74    /// revocation transaction metadata.
75    revoke: Option<SealedTransaction>,
76    /// encrypted data in blob format.
77    blob: SealedBlob,
78}
79
80impl<P: BoxProvider> DbView<P> {
81    /// Create a new [`DbView`] to interface with the [`Vault`] types in the database.
82    pub fn new() -> DbView<P> {
83        let vaults = HashMap::new();
84
85        Self { vaults }
86    }
87
88    /// Initialize a new [`Vault`] if it doesn't exist.
89    pub fn init_vault(&mut self, key: &Key<P>, vid: VaultId) {
90        self.vaults.entry(vid).or_insert_with(|| Vault::init_vault(key));
91    }
92
93    /// Write a new record to a [`Vault`]. Will instead update a [`Record`] if it already exists.
94    pub fn write(
95        &mut self,
96        key: &Key<P>,
97        vid: VaultId,
98        rid: RecordId,
99        data: &[u8],
100        record_hint: RecordHint,
101    ) -> Result<(), RecordError<P::Error>> {
102        if !self.vaults.contains_key(&vid) {
103            self.init_vault(key, vid);
104        }
105
106        let vault = self.vaults.get_mut(&vid).expect("Vault was initiated");
107        vault.add_or_update_record(key, rid.0, data, record_hint)
108    }
109
110    /// Lists all of the [`RecordHint`] values and [`RecordId`] values for the given [`Vault`].
111    pub fn list_hints_and_ids(&self, key: &Key<P>, vid: VaultId) -> Vec<(RecordId, RecordHint)> {
112        if let Some(vault) = self.vaults.get(&vid) {
113            vault.list_hints_and_ids(key)
114        } else {
115            vec![]
116        }
117    }
118
119    /// Check to see if a vault with the given [`VaultId`] is present.
120    pub fn contains_vault(&self, vid: &VaultId) -> bool {
121        self.vaults.contains_key(vid)
122    }
123
124    /// Check to see if a [`Vault`] contains a [`Record`] through the given [`RecordId`].
125    pub fn contains_record(&self, vid: VaultId, rid: RecordId) -> bool {
126        if let Some(vault) = self.vaults.get(&vid) {
127            vault.contains_record(rid)
128        } else {
129            false
130        }
131    }
132
133    /// Get the [`BlobId`] of the blob stored in the specified [`Record`].
134    /// The [`BlobId`] changes each time the record's content is updated.
135    pub fn get_blob_id(&self, key: &Key<P>, vid: VaultId, rid: RecordId) -> Result<BlobId, VaultError<P::Error>> {
136        let vault = self.vaults.get(&vid).ok_or(VaultError::VaultNotFound(vid))?;
137        let blob_id = vault.get_blob_id(key, rid.0)?;
138        Ok(blob_id)
139    }
140
141    /// Get the buffers for the given records, using the provided key for access.
142    fn get_buffers<E, const N: usize>(
143        &self,
144        ids: [(Key<P>, VaultId, RecordId); N],
145    ) -> Result<[Buffer<u8>; N], VaultError<P::Error, E>>
146    where
147        E: Debug,
148    {
149        let mut buffers: Vec<Buffer<u8>> = Vec::with_capacity(N);
150
151        for (key, vid, rid) in ids {
152            let vault = self.vaults.get(&vid).ok_or(VaultError::VaultNotFound(vid))?;
153            let guard = vault.get_guard(&key, rid.0).map_err(VaultError::Record)?;
154
155            buffers.push(guard);
156        }
157
158        let buffers: [Buffer<u8>; N] = <[_; N]>::try_from(buffers).expect("buffers did not have exactly len N");
159
160        Ok(buffers)
161    }
162
163    /// Get access the decrypted [`Buffer`] of the specified [`Record`].
164    pub fn get_guard<T, E, F>(
165        &self,
166        key: &Key<P>,
167        vid: VaultId,
168        rid: RecordId,
169        f: F,
170    ) -> Result<T, VaultError<P::Error, E>>
171    where
172        F: FnOnce(Buffer<u8>) -> Result<T, E>,
173        E: Debug,
174    {
175        let vault = self.vaults.get(&vid).ok_or(VaultError::VaultNotFound(vid))?;
176        let guard = vault.get_guard(key, rid.0).map_err(VaultError::Record)?;
177        f(guard).map_err(VaultError::Procedure)
178    }
179
180    pub fn get_guards<T, E, F, const N: usize>(
181        &self,
182        ids: [(Key<P>, VaultId, RecordId); N],
183        f: F,
184    ) -> Result<T, VaultError<P::Error, E>>
185    where
186        F: FnOnce([Buffer<u8>; N]) -> Result<T, E>,
187        E: Debug,
188    {
189        let buffers: [Buffer<u8>; N] = self.get_buffers(ids)?;
190        f(buffers).map_err(VaultError::Procedure)
191    }
192
193    /// Access the decrypted [`Buffer`]s of the specified [`Record`]s and place the return value
194    /// into the target [`Record`].
195    pub fn exec_procedure<T, E, F, const N: usize>(
196        &mut self,
197        sources: [(Key<P>, VaultId, RecordId); N],
198        target_key: &Key<P>,
199        target_vid: VaultId,
200        target_rid: RecordId,
201        hint: RecordHint,
202        f: F,
203    ) -> Result<T, VaultError<P::Error, E>>
204    where
205        F: FnOnce([Buffer<u8>; N]) -> Result<(Zeroizing<Vec<u8>>, T), E>,
206        E: Debug,
207    {
208        let buffers: [Buffer<u8>; N] = self.get_buffers(sources)?;
209
210        let (data, result) = f(buffers).map_err(VaultError::Procedure)?;
211
212        self.write(target_key, target_vid, target_rid, &data, hint)
213            .map_err(VaultError::Record)?;
214
215        Ok(result)
216    }
217
218    /// Add a revocation transaction to the [`Record`]
219    pub fn revoke_record(&mut self, key: &Key<P>, vid: VaultId, rid: RecordId) -> Result<(), RecordError<P::Error>> {
220        if let Some(vault) = self.vaults.get_mut(&vid) {
221            vault.revoke(key, rid.0)?;
222        }
223        Ok(())
224    }
225
226    /// Garbage collect a [`Vault`]. Deletes any records that contain revocation transactions.
227    pub fn garbage_collect_vault(&mut self, key: &Key<P>, vid: VaultId) {
228        if let Some(vault) = self.vaults.get_mut(&vid) {
229            if &vault.key == key {
230                vault.garbage_collect();
231            }
232        }
233    }
234
235    /// Clears the entire [`Vault`] from memory.
236    pub fn clear(&mut self) {
237        self.vaults.clear();
238    }
239
240    /// List the ids of all vaults.
241    pub fn list_vaults(&self) -> Vec<VaultId> {
242        self.vaults.keys().cloned().collect()
243    }
244
245    /// List the ids of all records in the vault.
246    pub fn list_records(&self, vid: &VaultId) -> Vec<RecordId> {
247        self.vaults
248            .get(vid)
249            .map(|v| v.entries.keys().map(|&c| c.into()).collect())
250            .unwrap_or_default()
251    }
252
253    /// List [`RecordId`] and [`BlobId`] of all entries in the vault.
254    pub fn list_records_with_blob_id(
255        &self,
256        key: &Key<P>,
257        vid: VaultId,
258    ) -> Result<Vec<(RecordId, BlobId)>, VaultError<P::Error>> {
259        self.vaults
260            .get(&vid)
261            .ok_or(VaultError::VaultNotFound(vid))
262            .and_then(|v| v.list_entries(key).map_err(|e| e.into()))
263    }
264
265    /// Clone the all records from all vaults without removing them.
266    pub fn export_all(&self) -> HashMap<VaultId, Vec<(RecordId, Record)>> {
267        self.vaults
268            .iter()
269            .map(|(vid, vault)| {
270                let entries = vault.entries.iter().map(|(&id, r)| (id.into(), r.clone())).collect();
271                (*vid, entries)
272            })
273            .collect()
274    }
275
276    /// Clone the specified records from the vault without removing them.
277    ///
278    /// **Note:** before importing these records to a new vault with [`DbView::import_records`], [`Record::update_meta`]
279    /// has to be called on each [`Record`] to re-encrypt it with the target vault's encryption key.
280    pub fn export_records<I>(&self, vid: VaultId, records: I) -> Result<Vec<(RecordId, Record)>, VaultError<P::Error>>
281    where
282        I: IntoIterator<Item = RecordId>,
283    {
284        let vault = self.vaults.get(&vid).ok_or(VaultError::VaultNotFound(vid))?;
285        let list = records
286            .into_iter()
287            .filter_map(|rid| vault.export_record(&rid).map(|r| (rid, r)))
288            .collect();
289        Ok(list)
290    }
291
292    /// Import records to the [`Vault`]. In case of duplicated records, the existing record is dropped in favor of the
293    /// new one. Re-encrypt the records with the new key.
294    pub fn import_records(
295        &mut self,
296        old_key: &Key<P>,
297        new_key: &Key<P>,
298        vid: VaultId,
299        mut records: Vec<(RecordId, Record)>,
300    ) -> Result<(), RecordError<P::Error>> {
301        if !self.vaults.contains_key(&vid) {
302            self.init_vault(new_key, vid);
303        }
304        for (rid, record) in &mut records {
305            record
306                .update_meta(old_key, (*rid).into(), new_key, (*rid).into())
307                .unwrap();
308        }
309        let vault = self.vaults.get_mut(&vid).expect("Vault was initiated.");
310        vault.extend(new_key, records.into_iter().map(|(rid, r)| (rid.0, r)))
311    }
312}
313
314impl<P: BoxProvider> Vault<P> {
315    /// Initialize a new [`Vault`]
316    pub fn init_vault(key: &Key<P>) -> Vault<P> {
317        let entries = HashMap::new();
318
319        Self {
320            entries,
321            key: key.clone(),
322        }
323    }
324
325    /// Adds a new [`Record`] to the [`Vault`] if the [`Record`] doesn't already exist. Otherwise, updates the data in
326    /// the existing [`Record`] as long as it hasn't been revoked.
327    pub fn add_or_update_record(
328        &mut self,
329        key: &Key<P>,
330        id: ChainId,
331        data: &[u8],
332        record_hint: RecordHint,
333    ) -> Result<(), RecordError<P::Error>> {
334        self.check_key(key)?;
335        let blob_id = BlobId::random::<P>().map_err(RecordError::Provider)?;
336        if let Some(entry) = self.entries.get_mut(&id) {
337            // TODO: double-check that using a new blob-id does not break the old snapshot format.
338            entry.update_data(key, id, data, blob_id)?
339        } else {
340            let entry = Record::new(key, id, blob_id, data, record_hint).map_err(RecordError::Provider)?;
341            self.entries.insert(id, entry);
342        }
343
344        Ok(())
345    }
346
347    /// Extend the stored entries with entries from another vault with the same key.
348    /// In case of duplicated records, the existing record is dropped in favor of the new one.
349    pub fn extend<I>(&mut self, key: &Key<P>, entries: I) -> Result<(), RecordError<P::Error>>
350    where
351        I: IntoIterator<Item = (ChainId, Record)>,
352    {
353        self.check_key(key)?;
354        self.entries.extend(entries.into_iter());
355        Ok(())
356    }
357
358    /// Export record stored in the current vault. This clones the encrypted record without
359    /// removing it.
360    pub fn export_record(&self, rid: &RecordId) -> Option<Record> {
361        self.entries.get(&rid.0).cloned()
362    }
363
364    /// List the [`RecordHint`] values and [`RecordId`] values of the specified [`Vault`].
365    pub(crate) fn list_hints_and_ids(&self, key: &Key<P>) -> Vec<(RecordId, RecordHint)> {
366        let mut buf: Vec<(RecordId, RecordHint)> = Vec::new();
367
368        if key == &self.key {
369            buf = self
370                .entries
371                .values()
372                .filter_map(|entry| entry.get_hint_and_id(key).ok())
373                .collect();
374        }
375
376        buf
377    }
378
379    /// List the [`RecordId`]s of the entries stored in this [`Vault`].
380    fn list_entries(&self, key: &Key<P>) -> Result<Vec<(RecordId, BlobId)>, RecordError<P::Error>> {
381        let mut buf = Vec::new();
382        for (&id, record) in self.entries.iter() {
383            let blob_id = record.get_blob_id(key, id)?;
384            buf.push((id.into(), blob_id))
385        }
386        Ok(buf)
387    }
388
389    /// Check if the [`Vault`] contains a [`Record`].
390    fn contains_record(&self, rid: RecordId) -> bool {
391        self.entries.values().any(|entry| entry.check_id(rid))
392    }
393
394    /// Revokes an [`Record`] by its [`ChainId`].  Does nothing if the [`Record`] doesn't exist.
395    pub fn revoke(&mut self, key: &Key<P>, id: ChainId) -> Result<(), RecordError<P::Error>> {
396        self.check_key(key)?;
397        if let Some(entry) = self.entries.get_mut(&id) {
398            entry.revoke(key, id)?;
399        }
400        Ok(())
401    }
402
403    /// Gets the decrypted [`Buffer`] from the [`Record`]
404    pub fn get_guard(&self, key: &Key<P>, id: ChainId) -> Result<Buffer<u8>, RecordError<P::Error>> {
405        self.check_key(key)?;
406        let entry = self.entries.get(&id).ok_or(RecordError::RecordNotFound(id))?;
407        entry.get_blob(key, id)
408    }
409
410    /// Sorts through all of the vault entries and garbage collects any revoked entries.
411    pub fn garbage_collect(&mut self) {
412        // get the keys of the entries with the revocation transactions.
413        let garbage: Vec<ChainId> = self
414            .entries
415            .iter()
416            .filter(|(_, entry)| entry.revoke.is_some())
417            .map(|(c, _)| *c)
418            .collect();
419
420        // remove the garbage entries from the database.
421        garbage.iter().for_each(|c| {
422            self.entries.remove(c);
423        });
424    }
425
426    /// Gets the [`BlobId`] of the record with the given [`ChainId`].
427    pub fn get_blob_id(&self, key: &Key<P>, id: ChainId) -> Result<BlobId, RecordError<P::Error>> {
428        self.check_key(key)?;
429        self.entries
430            .get(&id)
431            .ok_or(RecordError::RecordNotFound(id))
432            .and_then(|r| r.get_blob_id(key, id))
433    }
434
435    fn check_key(&self, key: &Key<P>) -> Result<(), RecordError<P::Error>> {
436        if key == &self.key {
437            Ok(())
438        } else {
439            Err(RecordError::InvalidKey)
440        }
441    }
442}
443
444impl Record {
445    // create a new [`Record`].
446    pub fn new<P: BoxProvider>(
447        key: &Key<P>,
448        id: ChainId,
449        blob: BlobId,
450        data: &[u8],
451        hint: RecordHint,
452    ) -> Result<Record, P::Error> {
453        let len = data.len() as u64;
454        let dtx = DataTransaction::new(id, len, blob, hint);
455
456        let blob: SealedBlob = data.encrypt(key, blob)?;
457        let data = dtx.encrypt(key, id)?;
458
459        Ok(Record {
460            id,
461            data,
462            blob,
463            revoke: None,
464        })
465    }
466
467    fn get_transaction<P: BoxProvider>(&self, key: &Key<P>) -> Result<Transaction, RecordError<P::Error>> {
468        // check if a revocation transaction exists.
469        if self.revoke.is_none() {
470            // decrypt data transaction.
471            self.data.decrypt(key, self.id).map_err(|err| match err {
472                DecryptError::Invalid => {
473                    RecordError::CorruptedContent("Could not convert bytes into transaction structure".into())
474                }
475                DecryptError::Provider(e) => RecordError::Provider(e),
476            })
477        } else {
478            Err(RecordError::RecordNotFound(self.id))
479        }
480    }
481
482    /// Gets the [`RecordHint`] and [`RecordId`] of the [`Record`].
483    fn get_hint_and_id<P: BoxProvider>(&self, key: &Key<P>) -> Result<(RecordId, RecordHint), RecordError<P::Error>> {
484        let tx = self.get_transaction(key)?;
485        let tx = tx.typed::<DataTransaction>().ok_or_else(|| {
486            RecordError::CorruptedContent("Could not type decrypted transaction as data-transaction".into())
487        })?;
488        let hint = tx.record_hint;
489        Ok((self.id.into(), hint))
490    }
491
492    /// Check to see if a [`RecordId`] pairs with the [`Record`]. Comes back as false if there is a revocation
493    /// transaction
494    fn check_id(&self, rid: RecordId) -> bool {
495        if self.revoke.is_none() {
496            rid.0 == self.id
497        } else {
498            false
499        }
500    }
501
502    /// Get the blob from this [`Record`].
503    fn get_blob<P: BoxProvider>(&self, key: &Key<P>, id: ChainId) -> Result<Buffer<u8>, RecordError<P::Error>> {
504        // check if ids match
505        if self.id != id {
506            return Err(RecordError::RecordNotFound(id));
507        }
508
509        let tx = self.get_transaction(key)?;
510        let tx = tx.typed::<DataTransaction>().ok_or_else(|| {
511            RecordError::CorruptedContent("Could not type decrypted transaction as data-transaction".into())
512        })?;
513
514        let blob = SealedBlob::from(self.blob.as_ref())
515            .decrypt(key, tx.blob)
516            .expect("Unable to decrypt blob");
517
518        let guarded = Buffer::alloc(&blob, tx.len.u64() as usize);
519
520        // let guarded =
521        //     GuardedVec::new(tx.len.u64() as usize, |i| {
522        //     let blob = SealedBlob::from(self.blob.as_ref())
523        //         .decrypt(key, tx.blob)
524        //         .expect("Unable to decrypt blob");
525
526        //     i.copy_from_slice(blob.as_ref());
527        // });
528
529        Ok(guarded)
530    }
531
532    /// Get the [`BlobId`] of a record. The [`BlobId`] changes each time the record is updated.
533    fn get_blob_id<P: BoxProvider>(&self, key: &Key<P>, id: ChainId) -> Result<BlobId, RecordError<P::Error>> {
534        // check if ids match
535        if self.id != id {
536            return Err(RecordError::RecordNotFound(id));
537        }
538        let tx = self.get_transaction(key)?;
539        let tx = tx.typed::<DataTransaction>().ok_or_else(|| {
540            RecordError::CorruptedContent("Could not type decrypted transaction as data-transaction".into())
541        })?;
542        Ok(tx.blob)
543    }
544
545    /// Update the data in an existing [`Record`].
546    fn update_data<P: BoxProvider>(
547        &mut self,
548        key: &Key<P>,
549        id: ChainId,
550        new_data: &[u8],
551        new_blob: BlobId,
552    ) -> Result<(), RecordError<P::Error>> {
553        // check if ids match
554        if self.id != id {
555            return Err(RecordError::RecordNotFound(id));
556        }
557
558        let tx = self.get_transaction(key)?;
559        let tx = tx.typed::<DataTransaction>().ok_or_else(|| {
560            RecordError::CorruptedContent("Could not type decrypted transaction as data-transaction".into())
561        })?;
562
563        // create a new sealed blob with the new_data.
564        let blob: SealedBlob = new_data.encrypt(key, new_blob).map_err(RecordError::Provider)?;
565
566        // create a new sealed transaction with the new_data length.
567        let dtx = DataTransaction::new(tx.id, new_data.len() as u64, new_blob, tx.record_hint);
568        let data = dtx.encrypt(key, tx.id).map_err(RecordError::Provider)?;
569
570        self.blob = blob;
571        self.data = data;
572
573        Ok(())
574    }
575
576    /// Update the key and id of an existing [`Record`].
577    pub fn update_meta<P: BoxProvider>(
578        &mut self,
579        old_key: &Key<P>,
580        old_id: ChainId,
581        new_key: &Key<P>,
582        new_id: ChainId,
583    ) -> Result<(), RecordError<P::Error>> {
584        // check if ids match
585        if self.id != old_id {
586            return Err(RecordError::RecordNotFound(old_id));
587        }
588
589        let tx = self.get_transaction(old_key)?;
590        let typed_tx = tx.typed::<DataTransaction>().ok_or_else(|| {
591            RecordError::CorruptedContent("Could not type decrypted transaction as data-transaction".into())
592        })?;
593
594        // Re-encrypt the blob with the new key.
595        let updated_blob = SealedBlob::from(self.blob.as_ref())
596            .decrypt(old_key, typed_tx.blob)
597            .map_err(|e| match e {
598                DecryptError::Provider(e) => RecordError::Provider(e),
599                DecryptError::Invalid => unreachable!("Vec<u8>: TryFrom<Vec<u8>> is infallible."),
600            })?
601            .encrypt(new_key, typed_tx.blob)
602            .map_err(RecordError::Provider)?;
603
604        // Re-encrypt meta data with new key.
605        let updated_data = DataTransaction::new(new_id, typed_tx.len, typed_tx.blob, typed_tx.record_hint)
606            .encrypt(new_key, new_id)
607            .map_err(RecordError::Provider)?;
608
609        self.blob = updated_blob;
610        self.data = updated_data;
611        self.id = new_id;
612
613        Ok(())
614    }
615
616    // add a revocation transaction to the [`Record`].
617    fn revoke<P: BoxProvider>(&mut self, key: &Key<P>, id: ChainId) -> Result<(), RecordError<P::Error>> {
618        // check if id and id match.
619        if self.id != id {
620            return Err(RecordError::RecordNotFound(id));
621        }
622
623        // check if revoke transaction already exists.
624        if self.revoke.is_none() {
625            let revoke = RevocationTransaction::new(self.id)
626                .encrypt(key, self.id)
627                .map_err(RecordError::Provider)?;
628            self.revoke = Some(revoke);
629        }
630
631        Ok(())
632    }
633}