core_crypto_keystore/connection/
mod.rs

1use std::fmt;
2use std::ops::Deref;
3
4use sha2::{Digest as _, Sha256};
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7pub mod platform {
8    cfg_if::cfg_if! {
9        if #[cfg(target_family = "wasm")] {
10            mod wasm;
11            pub use wasm::keystore_v_1_0_0;
12            pub use self::wasm::WasmConnection as KeystoreDatabaseConnection;
13            pub use wasm::storage;
14            pub use self::wasm::storage::WasmStorageTransaction as TransactionWrapper;
15
16            /// TODO: this is here only because it's needed for the cryptobox migration test.
17            ///       Once we drop cryptobox migration and the related test, drop this too.
18            pub use wasm::open_and_migrate_pre_v4;
19        } else {
20            mod generic;
21            pub use self::generic::SqlCipherConnection as KeystoreDatabaseConnection;
22            pub use self::generic::TransactionWrapper;
23        }
24    }
25}
26
27pub use self::platform::*;
28use crate::entities::{Entity, EntityFindParams, StringEntityId};
29use std::ops::DerefMut;
30
31use crate::entities::{EntityTransactionExt, UniqueEntity};
32use crate::transaction::KeystoreTransaction;
33use crate::{CryptoKeystoreError, CryptoKeystoreResult};
34use async_lock::{Mutex, MutexGuard, Semaphore};
35use std::sync::Arc;
36
37/// Limit on the length of a blob to be stored in the database.
38///
39/// This limit applies to both SQLCipher-backed stores and WASM.
40/// This limit is conservative on purpose when targeting WASM, as the lower bound that exists is Safari with a limit of 1GB per origin.
41///
42/// See: [SQLite limits](https://www.sqlite.org/limits.html)
43/// See: [IndexedDB limits](https://stackoverflow.com/a/63019999/1934177)
44pub const MAX_BLOB_LEN: usize = 1_000_000_000;
45
46#[cfg(not(target_family = "wasm"))]
47// ? Because of UniFFI async requirements, we need our keystore to be Send as well now
48pub trait DatabaseConnectionRequirements: Sized + Send {}
49#[cfg(target_family = "wasm")]
50// ? On the other hand, things cannot be Send on WASM because of platform restrictions (all things are copied across the FFI)
51pub trait DatabaseConnectionRequirements: Sized {}
52
53/// The key used to encrypt the database.
54#[derive(Clone, Zeroize, ZeroizeOnDrop, derive_more::From)]
55pub struct DatabaseKey([u8; Self::LEN]);
56
57impl DatabaseKey {
58    pub const LEN: usize = 32;
59
60    pub fn generate() -> DatabaseKey {
61        DatabaseKey(rand::random::<[u8; Self::LEN]>())
62    }
63}
64
65impl fmt::Debug for DatabaseKey {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
67        f.write_str("DatabaseKey(hash=")?;
68        for x in Sha256::digest(self).as_slice().iter().take(10) {
69            fmt::LowerHex::fmt(x, f)?
70        }
71        f.write_str("...)")
72    }
73}
74
75impl AsRef<[u8]> for DatabaseKey {
76    fn as_ref(&self) -> &[u8] {
77        &self.0
78    }
79}
80
81impl Deref for DatabaseKey {
82    type Target = [u8];
83
84    fn deref(&self) -> &Self::Target {
85        &self.0
86    }
87}
88
89impl TryFrom<&[u8]> for DatabaseKey {
90    type Error = CryptoKeystoreError;
91
92    fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
93        if buf.len() != Self::LEN {
94            Err(CryptoKeystoreError::InvalidDbKeySize {
95                expected: Self::LEN,
96                actual: buf.len(),
97            })
98        } else {
99            Ok(Self(buf.try_into().unwrap()))
100        }
101    }
102}
103
104#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
105#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
106pub trait DatabaseConnection<'a>: DatabaseConnectionRequirements {
107    type Connection: 'a;
108
109    async fn open(name: &str, key: &DatabaseKey) -> CryptoKeystoreResult<Self>;
110
111    async fn open_in_memory(key: &DatabaseKey) -> CryptoKeystoreResult<Self>;
112
113    async fn close(self) -> CryptoKeystoreResult<()>;
114
115    /// Default implementation of wipe
116    async fn wipe(self) -> CryptoKeystoreResult<()> {
117        self.close().await
118    }
119
120    fn check_buffer_size(size: usize) -> CryptoKeystoreResult<()> {
121        #[cfg(not(target_family = "wasm"))]
122        if size > i32::MAX as usize {
123            return Err(CryptoKeystoreError::BlobTooBig);
124        }
125
126        if size >= MAX_BLOB_LEN {
127            return Err(CryptoKeystoreError::BlobTooBig);
128        }
129
130        Ok(())
131    }
132}
133
134#[derive(Debug, Clone)]
135pub struct Connection {
136    pub(crate) conn: Arc<Mutex<KeystoreDatabaseConnection>>,
137    pub(crate) transaction: Arc<Mutex<Option<KeystoreTransaction>>>,
138    transaction_semaphore: Arc<Semaphore>,
139}
140
141const ALLOWED_CONCURRENT_TRANSACTIONS_COUNT: usize = 1;
142
143/// Interface to fetch from the database either from the connection directly or through a
144/// transaaction
145#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
146#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
147pub trait FetchFromDatabase: Send + Sync {
148    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
149        &self,
150        id: &[u8],
151    ) -> CryptoKeystoreResult<Option<E>>;
152
153    async fn find_unique<U: UniqueEntity<ConnectionType = KeystoreDatabaseConnection>>(
154        &self,
155    ) -> CryptoKeystoreResult<U>;
156
157    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
158        &self,
159        params: EntityFindParams,
160    ) -> CryptoKeystoreResult<Vec<E>>;
161
162    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
163        &self,
164        ids: &[Vec<u8>],
165    ) -> CryptoKeystoreResult<Vec<E>>;
166    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize>;
167}
168
169// SAFETY: this has mutexes and atomics protecting underlying data so this is safe to share between threads
170unsafe impl Send for Connection {}
171// SAFETY: this has mutexes and atomics protecting underlying data so this is safe to share between threads
172unsafe impl Sync for Connection {}
173
174/// Where to open a connection
175#[derive(Debug, Clone)]
176pub enum ConnectionType<'a> {
177    /// This connection is persistent at the provided path
178    Persistent(&'a str),
179    /// This connection is transient and lives in memory
180    InMemory,
181}
182
183impl Connection {
184    pub async fn open(location: ConnectionType<'_>, key: &DatabaseKey) -> CryptoKeystoreResult<Self> {
185        let conn = match location {
186            ConnectionType::Persistent(name) => KeystoreDatabaseConnection::open(name, key).await?.into(),
187            ConnectionType::InMemory => KeystoreDatabaseConnection::open_in_memory(key).await?.into(),
188        };
189        #[allow(clippy::arc_with_non_send_sync)] // see https://github.com/rustwasm/wasm-bindgen/pull/955
190        let conn = Arc::new(conn);
191        Ok(Self {
192            conn,
193            transaction: Default::default(),
194            transaction_semaphore: Arc::new(Semaphore::new(ALLOWED_CONCURRENT_TRANSACTIONS_COUNT)),
195        })
196    }
197
198    pub async fn borrow_conn(&self) -> CryptoKeystoreResult<MutexGuard<'_, KeystoreDatabaseConnection>> {
199        Ok(self.conn.lock().await)
200    }
201
202    pub async fn migrate_db_key_type_to_bytes(
203        name: &str,
204        old_key: &str,
205        new_key: &DatabaseKey,
206    ) -> CryptoKeystoreResult<()> {
207        KeystoreDatabaseConnection::migrate_db_key_type_to_bytes(name, old_key, new_key).await
208    }
209
210    pub async fn wipe(self) -> CryptoKeystoreResult<()> {
211        if self.transaction.lock().await.is_some() {
212            return Err(CryptoKeystoreError::TransactionInProgress {
213                attempted_operation: "wipe()".to_string(),
214            });
215        }
216        let conn: KeystoreDatabaseConnection = Arc::into_inner(self.conn).unwrap().into_inner();
217        conn.wipe().await?;
218        Ok(())
219    }
220
221    pub async fn can_close(&self) -> bool {
222        // transaction in progress
223        if self.transaction.lock().await.is_some() {
224            return false;
225        }
226        Arc::strong_count(&self.conn) <= 1
227    }
228
229    pub async fn close(self) -> CryptoKeystoreResult<()> {
230        if self.transaction.lock().await.is_some() {
231            return Err(CryptoKeystoreError::TransactionInProgress {
232                attempted_operation: "close()".to_string(),
233            });
234        }
235        let Some(conn) = Arc::into_inner(self.conn) else {
236            return Err(CryptoKeystoreError::CannotClose);
237        };
238        let conn = conn.into_inner();
239        conn.close().await?;
240        Ok(())
241    }
242
243    /// Waits for the current transaction to be committed or rolled back, then starts a new one.
244    pub async fn new_transaction(&self) -> CryptoKeystoreResult<()> {
245        let semaphore = self.transaction_semaphore.acquire_arc().await;
246        let mut transaction_guard = self.transaction.lock().await;
247        *transaction_guard = Some(KeystoreTransaction::new(semaphore).await?);
248        Ok(())
249    }
250
251    pub async fn commit_transaction(&self) -> CryptoKeystoreResult<()> {
252        let mut transaction_guard = self.transaction.lock().await;
253        let Some(transaction) = transaction_guard.as_ref() else {
254            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
255        };
256        transaction.commit(self).await?;
257        *transaction_guard = None;
258        Ok(())
259    }
260
261    pub async fn rollback_transaction(&self) -> CryptoKeystoreResult<()> {
262        let mut transaction_guard = self.transaction.lock().await;
263        if transaction_guard.is_none() {
264            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
265        };
266        *transaction_guard = None;
267        Ok(())
268    }
269
270    pub async fn child_groups<
271        E: Entity<ConnectionType = KeystoreDatabaseConnection> + crate::entities::PersistedMlsGroupExt + Sync,
272    >(
273        &self,
274        entity: E,
275    ) -> CryptoKeystoreResult<Vec<E>> {
276        let mut conn = self.conn.lock().await;
277        let persisted_records = entity.child_groups(conn.deref_mut()).await?;
278
279        let transaction_guard = self.transaction.lock().await;
280        let Some(transaction) = transaction_guard.as_ref() else {
281            return Ok(persisted_records);
282        };
283        transaction.child_groups(entity, persisted_records).await
284    }
285
286    pub async fn save<E: Entity<ConnectionType = KeystoreDatabaseConnection> + Sync + EntityTransactionExt>(
287        &self,
288        entity: E,
289    ) -> CryptoKeystoreResult<E> {
290        let transaction_guard = self.transaction.lock().await;
291        let Some(transaction) = transaction_guard.as_ref() else {
292            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
293        };
294        transaction.save_mut(entity).await
295    }
296
297    pub async fn remove<
298        E: Entity<ConnectionType = KeystoreDatabaseConnection> + EntityTransactionExt,
299        S: AsRef<[u8]>,
300    >(
301        &self,
302        id: S,
303    ) -> CryptoKeystoreResult<()> {
304        let transaction_guard = self.transaction.lock().await;
305        let Some(transaction) = transaction_guard.as_ref() else {
306            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
307        };
308        transaction.remove::<E, S>(id).await
309    }
310
311    pub async fn cred_delete_by_credential(&self, cred: Vec<u8>) -> CryptoKeystoreResult<()> {
312        let transaction_guard = self.transaction.lock().await;
313        let Some(transaction) = transaction_guard.as_ref() else {
314            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
315        };
316        transaction.cred_delete_by_credential(cred).await
317    }
318}
319
320#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
321#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
322impl FetchFromDatabase for Connection {
323    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
324        &self,
325        id: &[u8],
326    ) -> CryptoKeystoreResult<Option<E>> {
327        // If a transaction is in progress...
328        if let Some(transaction) = self.transaction.lock().await.as_ref() {
329            //... and it has information about this entity, ...
330            if let Some(result) = transaction.find::<E>(id).await? {
331                // ... return that result
332                return Ok(result);
333            }
334        }
335
336        // Otherwise get it from the database
337        let mut conn = self.conn.lock().await;
338        E::find_one(&mut conn, &id.into()).await
339    }
340
341    async fn find_unique<U: UniqueEntity>(&self) -> CryptoKeystoreResult<U> {
342        // If a transaction is in progress...
343        if let Some(transaction) = self.transaction.lock().await.as_ref() {
344            //... and it has information about this entity, ...
345            if let Some(result) = transaction.find_unique::<U>().await? {
346                // ... return that result
347                return Ok(result);
348            }
349        }
350        // Otherwise get it from the database
351        let mut conn = self.conn.lock().await;
352        U::find_unique(&mut conn).await
353    }
354
355    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
356        &self,
357        params: EntityFindParams,
358    ) -> CryptoKeystoreResult<Vec<E>> {
359        let mut conn = self.conn.lock().await;
360        let persisted_records = E::find_all(&mut conn, params.clone()).await?;
361
362        let transaction_guard = self.transaction.lock().await;
363        let Some(transaction) = transaction_guard.as_ref() else {
364            return Ok(persisted_records);
365        };
366        transaction.find_all(persisted_records, params).await
367    }
368
369    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
370        &self,
371        ids: &[Vec<u8>],
372    ) -> CryptoKeystoreResult<Vec<E>> {
373        let entity_ids: Vec<StringEntityId> = ids.iter().map(|id| id.as_slice().into()).collect();
374        let mut conn = self.conn.lock().await;
375        let persisted_records = E::find_many(&mut conn, &entity_ids).await?;
376
377        let transaction_guard = self.transaction.lock().await;
378        let Some(transaction) = transaction_guard.as_ref() else {
379            return Ok(persisted_records);
380        };
381        transaction.find_many(persisted_records, ids).await
382    }
383
384    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize> {
385        if self.transaction.lock().await.is_some() {
386            // Unfortunately, we have to do this because of possible record id overlap
387            // between cache and db.
388            return Ok(self.find_all::<E>(Default::default()).await?.len());
389        };
390        let mut conn = self.conn.lock().await;
391        E::count(&mut conn).await
392    }
393}