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