core_crypto_keystore/entities/
mls.rs

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