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