core_crypto_keystore/entities/
mls.rs

1use super::{Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId};
2use crate::{CryptoKeystoreError, CryptoKeystoreResult, connection::TransactionWrapper};
3use openmls_traits::types::SignatureScheme;
4use zeroize::Zeroize;
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)]
97pub struct MlsBufferedCommit {
98    // we'd ideally just call this field `conversation_id`, but as of right now the
99    // Entity macro does not yet support id columns not named `id`
100    #[id(hex, column = "conversation_id_hex")]
101    #[sensitive]
102    conversation_id: Vec<u8>,
103    commit_data: Vec<u8>,
104}
105
106impl MlsBufferedCommit {
107    /// Create a new `Self` from conversation id and the commit data.
108    pub fn new(conversation_id: Vec<u8>, commit_data: Vec<u8>) -> Self {
109        Self {
110            conversation_id,
111            commit_data,
112        }
113    }
114
115    pub fn conversation_id(&self) -> &[u8] {
116        &self.conversation_id
117    }
118
119    pub fn commit_data(&self) -> &[u8] {
120        &self.commit_data
121    }
122
123    pub fn into_commit_data(self) -> Vec<u8> {
124        self.commit_data
125    }
126}
127
128/// Entity representing a persisted `Credential`
129#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
130#[zeroize(drop)]
131pub struct MlsCredential {
132    #[sensitive]
133    pub id: Vec<u8>,
134    #[sensitive]
135    pub credential: Vec<u8>,
136    pub created_at: u64,
137}
138
139#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
140#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
141pub trait MlsCredentialExt: Entity {
142    async fn delete_by_credential(tx: &TransactionWrapper<'_>, credential: Vec<u8>) -> CryptoKeystoreResult<()>;
143}
144
145/// Entity representing a persisted `SignatureKeyPair`
146#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
147#[zeroize(drop)]
148pub struct MlsSignatureKeyPair {
149    pub signature_scheme: u16,
150    #[sensitive]
151    pub pk: Vec<u8>,
152    #[sensitive]
153    pub keypair: Vec<u8>,
154    #[sensitive]
155    pub credential_id: Vec<u8>,
156}
157
158impl MlsSignatureKeyPair {
159    pub fn new(signature_scheme: SignatureScheme, pk: Vec<u8>, keypair: Vec<u8>, credential_id: Vec<u8>) -> Self {
160        Self {
161            signature_scheme: signature_scheme as u16,
162            pk,
163            keypair,
164            credential_id,
165        }
166    }
167}
168
169/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
170#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
171#[zeroize(drop)]
172#[sensitive]
173pub struct MlsHpkePrivateKey {
174    pub sk: Vec<u8>,
175    pub pk: Vec<u8>,
176}
177
178/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
179#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
180#[zeroize(drop)]
181#[sensitive]
182pub struct MlsEncryptionKeyPair {
183    pub sk: Vec<u8>,
184    pub pk: Vec<u8>,
185}
186
187/// Entity representing a list of [MlsEncryptionKeyPair]
188#[derive(
189    core_crypto_macros::Debug,
190    Clone,
191    PartialEq,
192    Eq,
193    Zeroize,
194    core_crypto_macros::Entity,
195    serde::Serialize,
196    serde::Deserialize,
197)]
198#[zeroize(drop)]
199#[entity(collection_name = "mls_epoch_encryption_keypairs")]
200pub struct MlsEpochEncryptionKeyPair {
201    #[id(hex, column = "id_hex")]
202    pub id: Vec<u8>,
203    #[sensitive]
204    pub keypairs: Vec<u8>,
205}
206
207/// Entity representing a persisted `SignatureKeyPair`
208#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
209#[zeroize(drop)]
210#[sensitive]
211pub struct MlsPskBundle {
212    pub psk_id: Vec<u8>,
213    pub psk: Vec<u8>,
214}
215
216/// Entity representing a persisted `KeyPackage`
217#[derive(
218    core_crypto_macros::Debug,
219    Clone,
220    PartialEq,
221    Eq,
222    Zeroize,
223    core_crypto_macros::Entity,
224    serde::Serialize,
225    serde::Deserialize,
226)]
227#[zeroize(drop)]
228#[entity(collection_name = "mls_keypackages")]
229pub struct MlsKeyPackage {
230    #[id(hex, column = "keypackage_ref_hex")]
231    pub keypackage_ref: Vec<u8>,
232    #[sensitive]
233    pub keypackage: Vec<u8>,
234}
235
236/// Entity representing an enrollment instance used to fetch a x509 certificate and persisted when
237/// context switches and the memory it lives in is about to be erased
238#[derive(
239    core_crypto_macros::Debug,
240    Clone,
241    PartialEq,
242    Eq,
243    Zeroize,
244    core_crypto_macros::Entity,
245    serde::Serialize,
246    serde::Deserialize,
247)]
248#[zeroize(drop)]
249#[entity(collection_name = "e2ei_enrollment", no_upsert)]
250pub struct E2eiEnrollment {
251    pub id: Vec<u8>,
252    pub content: Vec<u8>,
253}
254
255#[cfg(target_family = "wasm")]
256#[async_trait::async_trait(?Send)]
257pub trait UniqueEntity:
258    EntityBase<ConnectionType = crate::connection::KeystoreDatabaseConnection>
259    + serde::Serialize
260    + serde::de::DeserializeOwned
261where
262    Self: 'static,
263{
264    const ID: [u8; 1] = [0];
265
266    fn content(&self) -> &[u8];
267
268    fn set_content(&mut self, content: Vec<u8>);
269
270    async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Self> {
271        Ok(conn
272            .storage()
273            .get(Self::COLLECTION_NAME, &Self::ID)
274            .await?
275            .ok_or(CryptoKeystoreError::NotFound(Self::COLLECTION_NAME, "".to_string()))?)
276    }
277
278    async fn find_all(conn: &mut Self::ConnectionType, _params: EntityFindParams) -> CryptoKeystoreResult<Vec<Self>> {
279        match Self::find_unique(conn).await {
280            Ok(record) => Ok(vec![record]),
281            Err(CryptoKeystoreError::NotFound(_, _)) => Ok(vec![]),
282            Err(err) => Err(err),
283        }
284    }
285
286    async fn find_one(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Option<Self>> {
287        match Self::find_unique(conn).await {
288            Ok(record) => Ok(Some(record)),
289            Err(CryptoKeystoreError::NotFound(_, _)) => Ok(None),
290            Err(err) => Err(err),
291        }
292    }
293
294    async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<usize> {
295        conn.storage().count(Self::COLLECTION_NAME).await
296    }
297
298    async fn replace<'a>(&'a self, transaction: &TransactionWrapper<'a>) -> CryptoKeystoreResult<()> {
299        transaction.save(self.clone()).await?;
300        Ok(())
301    }
302}
303
304#[cfg(not(target_family = "wasm"))]
305#[async_trait::async_trait]
306pub trait UniqueEntity: EntityBase<ConnectionType = crate::connection::KeystoreDatabaseConnection> {
307    const ID: usize = 0;
308
309    fn new(content: Vec<u8>) -> Self;
310
311    async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Self> {
312        let mut conn = conn.conn().await;
313        let transaction = conn.transaction()?;
314        use rusqlite::OptionalExtension as _;
315
316        let maybe_content = transaction
317            .query_row(
318                &format!("SELECT content FROM {} WHERE id = ?", Self::COLLECTION_NAME),
319                [Self::ID],
320                |r| r.get::<_, Vec<u8>>(0),
321            )
322            .optional()?;
323
324        if let Some(content) = maybe_content {
325            Ok(Self::new(content))
326        } else {
327            Err(CryptoKeystoreError::NotFound(Self::COLLECTION_NAME, "".to_string()))
328        }
329    }
330
331    async fn find_all(conn: &mut Self::ConnectionType, _params: EntityFindParams) -> CryptoKeystoreResult<Vec<Self>> {
332        match Self::find_unique(conn).await {
333            Ok(record) => Ok(vec![record]),
334            Err(CryptoKeystoreError::NotFound(_, _)) => Ok(vec![]),
335            Err(err) => Err(err),
336        }
337    }
338
339    async fn find_one(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Option<Self>> {
340        match Self::find_unique(conn).await {
341            Ok(record) => Ok(Some(record)),
342            Err(CryptoKeystoreError::NotFound(_, _)) => Ok(None),
343            Err(err) => Err(err),
344        }
345    }
346
347    async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<usize> {
348        let conn = conn.conn().await;
349        conn.query_row(&format!("SELECT COUNT(*) FROM {}", Self::COLLECTION_NAME), [], |r| {
350            r.get(0)
351        })
352        .map_err(Into::into)
353    }
354
355    fn content(&self) -> &[u8];
356
357    async fn replace(&self, transaction: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> {
358        use crate::connection::DatabaseConnection;
359        Self::ConnectionType::check_buffer_size(self.content().len())?;
360        let zb_content = rusqlite::blob::ZeroBlob(self.content().len() as i32);
361
362        use rusqlite::ToSql;
363        let params: [rusqlite::types::ToSqlOutput; 2] = [Self::ID.to_sql()?, zb_content.to_sql()?];
364
365        transaction.execute(
366            &format!(
367                "INSERT OR REPLACE INTO {} (id, content) VALUES (?, ?)",
368                Self::COLLECTION_NAME
369            ),
370            params,
371        )?;
372        let row_id = transaction.last_insert_rowid();
373
374        let mut blob = transaction.blob_open(
375            rusqlite::DatabaseName::Main,
376            Self::COLLECTION_NAME,
377            "content",
378            row_id,
379            false,
380        )?;
381        use std::io::Write;
382        blob.write_all(self.content())?;
383        blob.close()?;
384
385        Ok(())
386    }
387}
388
389#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
390#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
391impl<T: UniqueEntity + Send + Sync> EntityTransactionExt for T {
392    #[cfg(not(target_family = "wasm"))]
393    async fn save(&self, tx: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> {
394        self.replace(tx).await
395    }
396
397    #[cfg(target_family = "wasm")]
398    async fn save<'a>(&'a self, tx: &TransactionWrapper<'a>) -> CryptoKeystoreResult<()> {
399        self.replace(tx).await
400    }
401
402    #[cfg(not(target_family = "wasm"))]
403    async fn delete_fail_on_missing_id(
404        _: &TransactionWrapper<'_>,
405        _id: StringEntityId<'_>,
406    ) -> CryptoKeystoreResult<()> {
407        Err(CryptoKeystoreError::NotImplemented)
408    }
409
410    #[cfg(target_family = "wasm")]
411    async fn delete_fail_on_missing_id<'a>(
412        _: &TransactionWrapper<'a>,
413        _id: StringEntityId<'a>,
414    ) -> CryptoKeystoreResult<()> {
415        Err(CryptoKeystoreError::NotImplemented)
416    }
417}
418
419/// OIDC refresh token used in E2EI
420#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
421#[zeroize(drop)]
422pub struct E2eiRefreshToken {
423    pub content: Vec<u8>,
424}
425
426#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
427#[zeroize(drop)]
428pub struct E2eiAcmeCA {
429    pub content: Vec<u8>,
430}
431
432#[derive(
433    core_crypto_macros::Debug,
434    Clone,
435    PartialEq,
436    Eq,
437    Zeroize,
438    core_crypto_macros::Entity,
439    serde::Serialize,
440    serde::Deserialize,
441)]
442#[zeroize(drop)]
443pub struct E2eiIntermediateCert {
444    // key to identify the CA cert; Using a combination of SKI & AKI extensions concatenated like so is suitable: `SKI[+AKI]`
445    #[id]
446    pub ski_aki_pair: String,
447    pub content: Vec<u8>,
448}
449
450#[derive(
451    core_crypto_macros::Debug,
452    Clone,
453    PartialEq,
454    Eq,
455    Zeroize,
456    core_crypto_macros::Entity,
457    serde::Serialize,
458    serde::Deserialize,
459)]
460#[zeroize(drop)]
461pub struct E2eiCrl {
462    #[id]
463    pub distribution_point: String,
464    pub content: Vec<u8>,
465}