core_crypto_keystore/entities/
mls.rs

1use zeroize::Zeroize;
2
3use super::{Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId};
4use crate::{CryptoKeystoreError, CryptoKeystoreResult, connection::TransactionWrapper};
5
6/// Entity representing a persisted `MlsGroup`
7#[derive(
8    core_crypto_macros::Debug,
9    Clone,
10    PartialEq,
11    Eq,
12    Zeroize,
13    core_crypto_macros::Entity,
14    serde::Serialize,
15    serde::Deserialize,
16)]
17#[zeroize(drop)]
18#[entity(collection_name = "mls_groups")]
19#[sensitive]
20pub struct PersistedMlsGroup {
21    #[id(hex, column = "id_hex")]
22    pub id: Vec<u8>,
23    pub state: Vec<u8>,
24    pub parent_id: Option<Vec<u8>>,
25}
26
27#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
28#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
29pub trait PersistedMlsGroupExt: Entity {
30    fn parent_id(&self) -> Option<&[u8]>;
31
32    async fn parent_group(
33        &self,
34        conn: &mut <Self as super::EntityBase>::ConnectionType,
35    ) -> CryptoKeystoreResult<Option<Self>> {
36        let Some(parent_id) = self.parent_id() else {
37            return Ok(None);
38        };
39
40        <Self as super::Entity>::find_one(conn, &parent_id.into()).await
41    }
42
43    async fn child_groups(
44        &self,
45        conn: &mut <Self as super::EntityBase>::ConnectionType,
46    ) -> CryptoKeystoreResult<Vec<Self>> {
47        let entities = <Self as super::Entity>::find_all(conn, super::EntityFindParams::default()).await?;
48
49        let id = self.id_raw();
50
51        Ok(entities
52            .into_iter()
53            .filter(|entity| entity.parent_id().map(|parent_id| parent_id == id).unwrap_or_default())
54            .collect())
55    }
56}
57
58/// Entity representing a temporarily persisted `MlsGroup`
59#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
60#[zeroize(drop)]
61pub struct PersistedMlsPendingGroup {
62    #[sensitive]
63    pub id: Vec<u8>,
64    #[sensitive]
65    pub state: Vec<u8>,
66    #[sensitive]
67    pub parent_id: Option<Vec<u8>>,
68    pub custom_configuration: Vec<u8>,
69}
70
71/// Entity representing a buffered message
72#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
73#[zeroize(drop)]
74pub struct MlsPendingMessage {
75    #[sensitive]
76    pub foreign_id: Vec<u8>,
77    pub message: Vec<u8>,
78}
79
80/// Entity representing a buffered commit.
81///
82/// There should always exist either 0 or 1 of these in the store per conversation.
83/// Commits are buffered if not all proposals they reference have yet been received.
84///
85/// We don't automatically zeroize on drop because the commit data is still encrypted at this point;
86/// it is not risky to leave it in memory.
87#[derive(
88    core_crypto_macros::Debug,
89    Clone,
90    PartialEq,
91    Eq,
92    Zeroize,
93    core_crypto_macros::Entity,
94    serde::Serialize,
95    serde::Deserialize,
96)]
97#[entity(collection_name = "mls_buffered_commits")]
98pub struct StoredBufferedCommit {
99    // we'd ideally just call this field `conversation_id`, but as of right now the
100    // Entity macro does not yet support id columns not named `id`
101    #[id(hex, column = "conversation_id_hex")]
102    #[sensitive]
103    conversation_id: Vec<u8>,
104    commit_data: Vec<u8>,
105}
106
107impl StoredBufferedCommit {
108    /// Create a new `Self` from conversation id and the commit data.
109    pub fn new(conversation_id: Vec<u8>, commit_data: Vec<u8>) -> Self {
110        Self {
111            conversation_id,
112            commit_data,
113        }
114    }
115
116    pub fn conversation_id(&self) -> &[u8] {
117        &self.conversation_id
118    }
119
120    pub fn commit_data(&self) -> &[u8] {
121        &self.commit_data
122    }
123
124    pub fn into_commit_data(self) -> Vec<u8> {
125        self.commit_data
126    }
127}
128
129/// Entity representing a persisted `Credential`
130#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
131#[zeroize(drop)]
132pub struct StoredCredential {
133    /// Note: this is not a unique identifier, but the session id this credential belongs to.
134    #[sensitive]
135    pub id: Vec<u8>,
136    #[sensitive]
137    pub credential: Vec<u8>,
138    pub created_at: u64,
139    pub signature_scheme: u16,
140    #[sensitive]
141    pub public_key: Vec<u8>,
142    #[sensitive]
143    pub secret_key: Vec<u8>,
144}
145
146#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
147#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
148pub trait MlsCredentialExt: Entity {
149    async fn delete_by_credential(tx: &TransactionWrapper<'_>, credential: Vec<u8>) -> CryptoKeystoreResult<()>;
150}
151
152/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
153#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
154#[zeroize(drop)]
155#[sensitive]
156pub struct StoredHpkePrivateKey {
157    pub sk: Vec<u8>,
158    pub pk: Vec<u8>,
159}
160
161/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
162#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
163#[zeroize(drop)]
164#[sensitive]
165pub struct StoredEncryptionKeyPair {
166    pub sk: Vec<u8>,
167    pub pk: Vec<u8>,
168}
169
170/// Entity representing a list of [StoredEncryptionKeyPair]
171#[derive(
172    core_crypto_macros::Debug,
173    Clone,
174    PartialEq,
175    Eq,
176    Zeroize,
177    core_crypto_macros::Entity,
178    serde::Serialize,
179    serde::Deserialize,
180)]
181#[zeroize(drop)]
182#[entity(collection_name = "mls_epoch_encryption_keypairs")]
183pub struct StoredEpochEncryptionKeypair {
184    #[id(hex, column = "id_hex")]
185    pub id: Vec<u8>,
186    #[sensitive]
187    pub keypairs: Vec<u8>,
188}
189
190/// Entity representing a persisted `SignatureKeyPair`
191#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
192#[zeroize(drop)]
193#[sensitive]
194pub struct StoredPskBundle {
195    pub psk_id: Vec<u8>,
196    pub psk: Vec<u8>,
197}
198
199/// Entity representing a persisted `KeyPackage`
200#[derive(
201    core_crypto_macros::Debug,
202    Clone,
203    PartialEq,
204    Eq,
205    Zeroize,
206    core_crypto_macros::Entity,
207    serde::Serialize,
208    serde::Deserialize,
209)]
210#[zeroize(drop)]
211#[entity(collection_name = "mls_keypackages")]
212pub struct StoredKeypackage {
213    #[id(hex, column = "keypackage_ref_hex")]
214    pub keypackage_ref: Vec<u8>,
215    #[sensitive]
216    pub keypackage: Vec<u8>,
217}
218
219/// Entity representing an enrollment instance used to fetch a x509 certificate and persisted when
220/// context switches and the memory it lives in is about to be erased
221#[derive(
222    core_crypto_macros::Debug,
223    Clone,
224    PartialEq,
225    Eq,
226    Zeroize,
227    core_crypto_macros::Entity,
228    serde::Serialize,
229    serde::Deserialize,
230)]
231#[zeroize(drop)]
232#[entity(collection_name = "e2ei_enrollment", no_upsert)]
233pub struct StoredE2eiEnrollment {
234    pub id: Vec<u8>,
235    pub content: Vec<u8>,
236}
237
238#[cfg(target_family = "wasm")]
239#[async_trait::async_trait(?Send)]
240pub trait UniqueEntity:
241    EntityBase<ConnectionType = crate::connection::KeystoreDatabaseConnection>
242    + serde::Serialize
243    + serde::de::DeserializeOwned
244where
245    Self: 'static,
246{
247    const ID: [u8; 1] = [0];
248
249    fn content(&self) -> &[u8];
250
251    fn set_content(&mut self, content: Vec<u8>);
252
253    async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Self> {
254        Ok(conn
255            .storage()
256            .get(Self::COLLECTION_NAME, &Self::ID)
257            .await?
258            .ok_or(CryptoKeystoreError::NotFound(Self::COLLECTION_NAME, "".to_string()))?)
259    }
260
261    async fn find_all(conn: &mut Self::ConnectionType, _params: EntityFindParams) -> CryptoKeystoreResult<Vec<Self>> {
262        match Self::find_unique(conn).await {
263            Ok(record) => Ok(vec![record]),
264            Err(CryptoKeystoreError::NotFound(..)) => Ok(vec![]),
265            Err(err) => Err(err),
266        }
267    }
268
269    async fn find_one(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Option<Self>> {
270        match Self::find_unique(conn).await {
271            Ok(record) => Ok(Some(record)),
272            Err(CryptoKeystoreError::NotFound(..)) => Ok(None),
273            Err(err) => Err(err),
274        }
275    }
276
277    async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<usize> {
278        conn.storage().count(Self::COLLECTION_NAME).await
279    }
280
281    async fn replace<'a>(&'a self, transaction: &TransactionWrapper<'a>) -> CryptoKeystoreResult<()> {
282        transaction.save(self.clone()).await?;
283        Ok(())
284    }
285}
286
287#[cfg(not(target_family = "wasm"))]
288#[async_trait::async_trait]
289pub trait UniqueEntity: EntityBase<ConnectionType = crate::connection::KeystoreDatabaseConnection> {
290    const ID: usize = 0;
291
292    fn new(content: Vec<u8>) -> Self;
293
294    async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Self> {
295        let mut conn = conn.conn().await;
296        let transaction = conn.transaction()?;
297        use rusqlite::OptionalExtension as _;
298
299        let maybe_content = transaction
300            .query_row(
301                &format!("SELECT content FROM {} WHERE id = ?", Self::COLLECTION_NAME),
302                [Self::ID],
303                |r| r.get::<_, Vec<u8>>(0),
304            )
305            .optional()?;
306
307        if let Some(content) = maybe_content {
308            Ok(Self::new(content))
309        } else {
310            Err(CryptoKeystoreError::NotFound(Self::COLLECTION_NAME, "".to_string()))
311        }
312    }
313
314    async fn find_all(conn: &mut Self::ConnectionType, _params: EntityFindParams) -> CryptoKeystoreResult<Vec<Self>> {
315        match Self::find_unique(conn).await {
316            Ok(record) => Ok(vec![record]),
317            Err(CryptoKeystoreError::NotFound(..)) => Ok(vec![]),
318            Err(err) => Err(err),
319        }
320    }
321
322    async fn find_one(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Option<Self>> {
323        match Self::find_unique(conn).await {
324            Ok(record) => Ok(Some(record)),
325            Err(CryptoKeystoreError::NotFound(..)) => Ok(None),
326            Err(err) => Err(err),
327        }
328    }
329
330    async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<usize> {
331        let conn = conn.conn().await;
332        conn.query_row(&format!("SELECT COUNT(*) FROM {}", Self::COLLECTION_NAME), [], |r| {
333            r.get(0)
334        })
335        .map_err(Into::into)
336    }
337
338    fn content(&self) -> &[u8];
339
340    async fn replace(&self, transaction: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> {
341        use crate::connection::DatabaseConnection;
342        Self::ConnectionType::check_buffer_size(self.content().len())?;
343        let zb_content = rusqlite::blob::ZeroBlob(self.content().len() as i32);
344
345        use rusqlite::ToSql;
346        let params: [rusqlite::types::ToSqlOutput; 2] = [Self::ID.to_sql()?, zb_content.to_sql()?];
347
348        transaction.execute(
349            &format!(
350                "INSERT OR REPLACE INTO {} (id, content) VALUES (?, ?)",
351                Self::COLLECTION_NAME
352            ),
353            params,
354        )?;
355        let row_id = transaction.last_insert_rowid();
356
357        let mut blob = transaction.blob_open(rusqlite::MAIN_DB, Self::COLLECTION_NAME, "content", row_id, false)?;
358        use std::io::Write;
359        blob.write_all(self.content())?;
360        blob.close()?;
361
362        Ok(())
363    }
364}
365
366#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
367#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
368impl<T: UniqueEntity + Send + Sync> EntityTransactionExt for T {
369    #[cfg(not(target_family = "wasm"))]
370    async fn save(&self, tx: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> {
371        self.replace(tx).await
372    }
373
374    #[cfg(target_family = "wasm")]
375    async fn save<'a>(&'a self, tx: &TransactionWrapper<'a>) -> CryptoKeystoreResult<()> {
376        self.replace(tx).await
377    }
378
379    #[cfg(not(target_family = "wasm"))]
380    async fn delete_fail_on_missing_id(
381        _: &TransactionWrapper<'_>,
382        _id: StringEntityId<'_>,
383    ) -> CryptoKeystoreResult<()> {
384        Err(CryptoKeystoreError::NotImplemented)
385    }
386
387    #[cfg(target_family = "wasm")]
388    async fn delete_fail_on_missing_id<'a>(
389        _: &TransactionWrapper<'a>,
390        _id: StringEntityId<'a>,
391    ) -> CryptoKeystoreResult<()> {
392        Err(CryptoKeystoreError::NotImplemented)
393    }
394}
395
396/// OIDC refresh token used in E2EI
397#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
398#[zeroize(drop)]
399pub struct E2eiRefreshToken {
400    pub content: Vec<u8>,
401}
402
403#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
404#[zeroize(drop)]
405pub struct E2eiAcmeCA {
406    pub content: Vec<u8>,
407}
408
409#[derive(
410    core_crypto_macros::Debug,
411    Clone,
412    PartialEq,
413    Eq,
414    Zeroize,
415    core_crypto_macros::Entity,
416    serde::Serialize,
417    serde::Deserialize,
418)]
419#[zeroize(drop)]
420pub struct E2eiIntermediateCert {
421    // key to identify the CA cert; Using a combination of SKI & AKI extensions concatenated like so is suitable:
422    // `SKI[+AKI]`
423    #[id]
424    pub ski_aki_pair: String,
425    pub content: Vec<u8>,
426}
427
428#[derive(
429    core_crypto_macros::Debug,
430    Clone,
431    PartialEq,
432    Eq,
433    Zeroize,
434    core_crypto_macros::Entity,
435    serde::Serialize,
436    serde::Deserialize,
437)]
438#[zeroize(drop)]
439pub struct E2eiCrl {
440    #[id]
441    pub distribution_point: String,
442    pub content: Vec<u8>,
443}