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 update_key(&mut self, new_key: &DatabaseKey) -> CryptoKeystoreResult<()>;
114
115    async fn close(self) -> CryptoKeystoreResult<()>;
116
117    /// Default implementation of wipe
118    async fn wipe(self) -> CryptoKeystoreResult<()> {
119        self.close().await
120    }
121
122    fn check_buffer_size(size: usize) -> CryptoKeystoreResult<()> {
123        #[cfg(not(target_family = "wasm"))]
124        if size > i32::MAX as usize {
125            return Err(CryptoKeystoreError::BlobTooBig);
126        }
127
128        if size >= MAX_BLOB_LEN {
129            return Err(CryptoKeystoreError::BlobTooBig);
130        }
131
132        Ok(())
133    }
134}
135
136#[derive(Debug, Clone)]
137pub struct Connection {
138    pub(crate) conn: Arc<Mutex<KeystoreDatabaseConnection>>,
139    pub(crate) transaction: Arc<Mutex<Option<KeystoreTransaction>>>,
140    transaction_semaphore: Arc<Semaphore>,
141}
142
143const ALLOWED_CONCURRENT_TRANSACTIONS_COUNT: usize = 1;
144
145/// Interface to fetch from the database either from the connection directly or through a
146/// transaaction
147#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
148#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
149pub trait FetchFromDatabase: Send + Sync {
150    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
151        &self,
152        id: &[u8],
153    ) -> CryptoKeystoreResult<Option<E>>;
154
155    async fn find_unique<U: UniqueEntity<ConnectionType = KeystoreDatabaseConnection>>(
156        &self,
157    ) -> CryptoKeystoreResult<U>;
158
159    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
160        &self,
161        params: EntityFindParams,
162    ) -> CryptoKeystoreResult<Vec<E>>;
163
164    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
165        &self,
166        ids: &[Vec<u8>],
167    ) -> CryptoKeystoreResult<Vec<E>>;
168    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize>;
169}
170
171// SAFETY: this has mutexes and atomics protecting underlying data so this is safe to share between threads
172unsafe impl Send for Connection {}
173// SAFETY: this has mutexes and atomics protecting underlying data so this is safe to share between threads
174unsafe impl Sync for Connection {}
175
176/// Where to open a connection
177#[derive(Debug, Clone)]
178pub enum ConnectionType<'a> {
179    /// This connection is persistent at the provided path
180    Persistent(&'a str),
181    /// This connection is transient and lives in memory
182    InMemory,
183}
184
185impl Connection {
186    pub async fn open(location: ConnectionType<'_>, key: &DatabaseKey) -> CryptoKeystoreResult<Self> {
187        let conn = match location {
188            ConnectionType::Persistent(name) => KeystoreDatabaseConnection::open(name, key).await?.into(),
189            ConnectionType::InMemory => KeystoreDatabaseConnection::open_in_memory(key).await?.into(),
190        };
191        #[allow(clippy::arc_with_non_send_sync)] // see https://github.com/rustwasm/wasm-bindgen/pull/955
192        let conn = Arc::new(conn);
193        Ok(Self {
194            conn,
195            transaction: Default::default(),
196            transaction_semaphore: Arc::new(Semaphore::new(ALLOWED_CONCURRENT_TRANSACTIONS_COUNT)),
197        })
198    }
199
200    pub async fn borrow_conn(&self) -> CryptoKeystoreResult<MutexGuard<'_, KeystoreDatabaseConnection>> {
201        Ok(self.conn.lock().await)
202    }
203
204    pub async fn migrate_db_key_type_to_bytes(
205        name: &str,
206        old_key: &str,
207        new_key: &DatabaseKey,
208    ) -> CryptoKeystoreResult<()> {
209        KeystoreDatabaseConnection::migrate_db_key_type_to_bytes(name, old_key, new_key).await
210    }
211
212    pub async fn update_key(&mut self, new_key: &DatabaseKey) -> CryptoKeystoreResult<()> {
213        self.conn.lock().await.update_key(new_key).await
214    }
215
216    pub async fn wipe(self) -> CryptoKeystoreResult<()> {
217        if self.transaction.lock().await.is_some() {
218            return Err(CryptoKeystoreError::TransactionInProgress {
219                attempted_operation: "wipe()".to_string(),
220            });
221        }
222        let conn: KeystoreDatabaseConnection = Arc::into_inner(self.conn).unwrap().into_inner();
223        conn.wipe().await?;
224        Ok(())
225    }
226
227    /// Wait for any running transaction to finish, then close the database connection.
228    pub async fn close(self) -> CryptoKeystoreResult<()> {
229        // Wait for any running transaction to finish
230        let _semaphore = self.transaction_semaphore.acquire_arc().await;
231        // Ensure that there's only one reference to the connection
232        let Some(conn) = Arc::into_inner(self.conn) else {
233            return Err(CryptoKeystoreError::CannotClose);
234        };
235        let conn = conn.into_inner();
236        conn.close().await?;
237        Ok(())
238    }
239
240    /// Waits for the current transaction to be committed or rolled back, then starts a new one.
241    pub async fn new_transaction(&self) -> CryptoKeystoreResult<()> {
242        let semaphore = self.transaction_semaphore.acquire_arc().await;
243        let mut transaction_guard = self.transaction.lock().await;
244        *transaction_guard = Some(KeystoreTransaction::new(semaphore).await?);
245        Ok(())
246    }
247
248    pub async fn commit_transaction(&self) -> CryptoKeystoreResult<()> {
249        let mut transaction_guard = self.transaction.lock().await;
250        let Some(transaction) = transaction_guard.as_ref() else {
251            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
252        };
253        transaction.commit(self).await?;
254        *transaction_guard = None;
255        Ok(())
256    }
257
258    pub async fn rollback_transaction(&self) -> CryptoKeystoreResult<()> {
259        let mut transaction_guard = self.transaction.lock().await;
260        if transaction_guard.is_none() {
261            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
262        };
263        *transaction_guard = None;
264        Ok(())
265    }
266
267    pub async fn child_groups<
268        E: Entity<ConnectionType = KeystoreDatabaseConnection> + crate::entities::PersistedMlsGroupExt + Sync,
269    >(
270        &self,
271        entity: E,
272    ) -> CryptoKeystoreResult<Vec<E>> {
273        let mut conn = self.conn.lock().await;
274        let persisted_records = entity.child_groups(conn.deref_mut()).await?;
275
276        let transaction_guard = self.transaction.lock().await;
277        let Some(transaction) = transaction_guard.as_ref() else {
278            return Ok(persisted_records);
279        };
280        transaction.child_groups(entity, persisted_records).await
281    }
282
283    pub async fn save<E: Entity<ConnectionType = KeystoreDatabaseConnection> + Sync + EntityTransactionExt>(
284        &self,
285        entity: E,
286    ) -> CryptoKeystoreResult<E> {
287        let transaction_guard = self.transaction.lock().await;
288        let Some(transaction) = transaction_guard.as_ref() else {
289            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
290        };
291        transaction.save_mut(entity).await
292    }
293
294    pub async fn remove<
295        E: Entity<ConnectionType = KeystoreDatabaseConnection> + EntityTransactionExt,
296        S: AsRef<[u8]>,
297    >(
298        &self,
299        id: S,
300    ) -> CryptoKeystoreResult<()> {
301        let transaction_guard = self.transaction.lock().await;
302        let Some(transaction) = transaction_guard.as_ref() else {
303            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
304        };
305        transaction.remove::<E, S>(id).await
306    }
307
308    pub async fn cred_delete_by_credential(&self, cred: Vec<u8>) -> CryptoKeystoreResult<()> {
309        let transaction_guard = self.transaction.lock().await;
310        let Some(transaction) = transaction_guard.as_ref() else {
311            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
312        };
313        transaction.cred_delete_by_credential(cred).await
314    }
315}
316
317#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
318#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
319impl FetchFromDatabase for Connection {
320    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
321        &self,
322        id: &[u8],
323    ) -> CryptoKeystoreResult<Option<E>> {
324        // If a transaction is in progress...
325        if let Some(transaction) = self.transaction.lock().await.as_ref() {
326            //... and it has information about this entity, ...
327            if let Some(result) = transaction.find::<E>(id).await? {
328                // ... return that result
329                return Ok(result);
330            }
331        }
332
333        // Otherwise get it from the database
334        let mut conn = self.conn.lock().await;
335        E::find_one(&mut conn, &id.into()).await
336    }
337
338    async fn find_unique<U: UniqueEntity>(&self) -> CryptoKeystoreResult<U> {
339        // If a transaction is in progress...
340        if let Some(transaction) = self.transaction.lock().await.as_ref() {
341            //... and it has information about this entity, ...
342            if let Some(result) = transaction.find_unique::<U>().await? {
343                // ... return that result
344                return Ok(result);
345            }
346        }
347        // Otherwise get it from the database
348        let mut conn = self.conn.lock().await;
349        U::find_unique(&mut conn).await
350    }
351
352    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
353        &self,
354        params: EntityFindParams,
355    ) -> CryptoKeystoreResult<Vec<E>> {
356        let mut conn = self.conn.lock().await;
357        let persisted_records = E::find_all(&mut conn, params.clone()).await?;
358
359        let transaction_guard = self.transaction.lock().await;
360        let Some(transaction) = transaction_guard.as_ref() else {
361            return Ok(persisted_records);
362        };
363        transaction.find_all(persisted_records, params).await
364    }
365
366    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
367        &self,
368        ids: &[Vec<u8>],
369    ) -> CryptoKeystoreResult<Vec<E>> {
370        let entity_ids: Vec<StringEntityId> = ids.iter().map(|id| id.as_slice().into()).collect();
371        let mut conn = self.conn.lock().await;
372        let persisted_records = E::find_many(&mut conn, &entity_ids).await?;
373
374        let transaction_guard = self.transaction.lock().await;
375        let Some(transaction) = transaction_guard.as_ref() else {
376            return Ok(persisted_records);
377        };
378        transaction.find_many(persisted_records, ids).await
379    }
380
381    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize> {
382        if self.transaction.lock().await.is_some() {
383            // Unfortunately, we have to do this because of possible record id overlap
384            // between cache and db.
385            return Ok(self.find_all::<E>(Default::default()).await?.len());
386        };
387        let mut conn = self.conn.lock().await;
388        E::count(&mut conn).await
389    }
390}