core_crypto_keystore/entities/
mls.rs

1use zeroize::{Zeroize, ZeroizeOnDrop};
2
3use crate::{
4    CryptoKeystoreError, CryptoKeystoreResult,
5    connection::TransactionWrapper,
6    traits::{BorrowPrimaryKey, Entity, EntityBase, KeyType, OwnedKeyType, PrimaryKey},
7};
8
9/// Entity representing a persisted `MlsGroup`
10#[derive(
11    core_crypto_macros::Debug,
12    Clone,
13    PartialEq,
14    Eq,
15    Zeroize,
16    core_crypto_macros::Entity,
17    core_crypto_macros::EntityNew,
18    serde::Serialize,
19    serde::Deserialize,
20)]
21#[zeroize(drop)]
22#[entity(collection_name = "mls_groups")]
23#[sensitive]
24pub struct PersistedMlsGroup {
25    #[entity(id, hex, column = "id_hex")]
26    #[id(hex, column = "id_hex")]
27    pub id: Vec<u8>,
28    pub state: Vec<u8>,
29    pub parent_id: Option<Vec<u8>>,
30}
31
32#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
33#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
34pub trait PersistedMlsGroupExt: Entity + BorrowPrimaryKey
35where
36    for<'a> &'a <Self as BorrowPrimaryKey>::BorrowedPrimaryKey: KeyType,
37{
38    fn parent_id(&self) -> Option<&[u8]>;
39
40    async fn parent_group(&self, conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Option<Self>> {
41        let Some(parent_id) = self.parent_id() else {
42            return Ok(None);
43        };
44
45        let parent_id = OwnedKeyType::from_bytes(parent_id)
46            .ok_or(CryptoKeystoreError::InvalidPrimaryKeyBytes(Self::COLLECTION_NAME))?;
47        Self::get(conn, &parent_id).await
48    }
49
50    async fn child_groups(&self, conn: &mut <Self as EntityBase>::ConnectionType) -> CryptoKeystoreResult<Vec<Self>> {
51        // A perfect opportunity for refactoring in WPB-20844
52        // when we do that, we no longer need varying implementations according to wasm or not,
53        // so both `parent_group` and this method should just be implemented directly on `PersistedMlsGroup`.
54        let entities = Self::load_all(conn).await?;
55
56        // for whatever reason rustc needs each of these distinct bindings to prove to itself that the lifetimes work
57        // out
58        let id = self.borrow_primary_key();
59        let id = id.bytes();
60        let id = id.as_ref();
61
62        Ok(entities
63            .into_iter()
64            .filter(|entity| entity.parent_id().map(|parent_id| parent_id == id).unwrap_or_default())
65            .collect())
66    }
67}
68
69/// Entity representing a temporarily persisted `MlsGroup`
70#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
71#[zeroize(drop)]
72pub struct PersistedMlsPendingGroup {
73    #[sensitive]
74    pub id: Vec<u8>,
75    #[sensitive]
76    pub state: Vec<u8>,
77    #[sensitive]
78    pub parent_id: Option<Vec<u8>>,
79    pub custom_configuration: Vec<u8>,
80}
81
82/// [`MlsPendingMessage`]s have no distinct primary key;
83/// they must always be accessed via [`MlsPendingMessage::find_all_by_conversation_id`] and
84/// cleaned up with [`MlsPendingMessage::delete_by_conversation_id`]
85///
86/// However, we have to fake a primary key type in order to support
87/// `KeystoreTransaction::remove_pending_messages_by_conversation_id`. Additionally we need the same one in WASM, where
88/// it's necessary for item-level encryption.
89///
90/// This implementation is fairly inefficient and hopefully temporary. But it at least implements the correct semantics.
91#[derive(ZeroizeOnDrop)]
92pub struct MlsPendingMessagePrimaryKey {
93    pub(crate) foreign_id: Vec<u8>,
94    message: Vec<u8>,
95}
96
97impl MlsPendingMessagePrimaryKey {
98    /// Construct a partial mls pending message primary key from only the conversation id.
99    ///
100    /// This does not in fact uniquely identify a single pending message--it should always uniquely
101    /// identify exactly 0 pending messages--but we have to have it so that we can search and delete
102    /// by conversation id within transactions.
103    pub(crate) fn from_conversation_id(conversation_id: impl AsRef<[u8]>) -> Self {
104        Self {
105            foreign_id: conversation_id.as_ref().to_owned(),
106            message: Vec::new(),
107        }
108    }
109}
110
111impl From<&MlsPendingMessage> for MlsPendingMessagePrimaryKey {
112    fn from(value: &MlsPendingMessage) -> Self {
113        Self {
114            foreign_id: value.foreign_id.clone(),
115            message: value.message.clone(),
116        }
117    }
118}
119
120impl KeyType for MlsPendingMessagePrimaryKey {
121    fn bytes(&self) -> std::borrow::Cow<'_, [u8]> {
122        // run-length encoding: 32 bits of size for each field, followed by the field
123        let fields = [&self.foreign_id, &self.message];
124        let mut key = Vec::with_capacity(
125            ((u32::BITS / u8::BITS) as usize * fields.len()) + self.foreign_id.len() + self.message.len(),
126        );
127        for field in fields {
128            key.extend((field.len() as u32).to_le_bytes());
129            key.extend(field.as_slice());
130        }
131        key.into()
132    }
133}
134
135impl OwnedKeyType for MlsPendingMessagePrimaryKey {
136    fn from_bytes(bytes: &[u8]) -> Option<Self> {
137        // run-length decoding: 32 bits of size for each field, followed by the field
138        let (len, bytes) = bytes.split_at_checked(4)?;
139        let len = u32::from_le_bytes(len.try_into().ok()?);
140        let (foreign_id, bytes) = bytes.split_at_checked(len as _)?;
141
142        let (len, bytes) = bytes.split_at_checked(4)?;
143        let len = u32::from_le_bytes(len.try_into().ok()?);
144        let (message, bytes) = bytes.split_at_checked(len as _)?;
145
146        bytes.is_empty().then(|| Self {
147            foreign_id: foreign_id.to_owned(),
148            message: message.to_owned(),
149        })
150    }
151}
152
153/// Entity representing a buffered message
154#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
155#[zeroize(drop)]
156pub struct MlsPendingMessage {
157    #[sensitive]
158    pub foreign_id: Vec<u8>,
159    pub message: Vec<u8>,
160}
161
162impl PrimaryKey for MlsPendingMessage {
163    type PrimaryKey = MlsPendingMessagePrimaryKey;
164    fn primary_key(&self) -> Self::PrimaryKey {
165        self.into()
166    }
167}
168
169/// Entity representing a buffered commit.
170///
171/// There should always exist either 0 or 1 of these in the store per conversation.
172/// Commits are buffered if not all proposals they reference have yet been received.
173///
174/// We don't automatically zeroize on drop because the commit data is still encrypted at this point;
175/// it is not risky to leave it in memory.
176#[derive(
177    core_crypto_macros::Debug,
178    Clone,
179    PartialEq,
180    Eq,
181    Zeroize,
182    core_crypto_macros::Entity,
183    core_crypto_macros::EntityNew,
184    serde::Serialize,
185    serde::Deserialize,
186)]
187#[entity(collection_name = "mls_buffered_commits")]
188pub struct StoredBufferedCommit {
189    #[entity(id, hex, column = "conversation_id_hex")]
190    #[id(hex, column = "conversation_id_hex")]
191    #[sensitive]
192    conversation_id: Vec<u8>,
193    commit_data: Vec<u8>,
194}
195
196impl StoredBufferedCommit {
197    /// Create a new `Self` from conversation id and the commit data.
198    pub fn new(conversation_id: Vec<u8>, commit_data: Vec<u8>) -> Self {
199        Self {
200            conversation_id,
201            commit_data,
202        }
203    }
204
205    pub fn conversation_id(&self) -> &[u8] {
206        &self.conversation_id
207    }
208
209    pub fn commit_data(&self) -> &[u8] {
210        &self.commit_data
211    }
212
213    pub fn into_commit_data(self) -> Vec<u8> {
214        self.commit_data
215    }
216}
217
218/// Entity representing a persisted `Credential`
219#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
220#[zeroize(drop)]
221pub struct StoredCredential {
222    /// Note: this is not a unique identifier, but the session id this credential belongs to.
223    #[sensitive]
224    pub session_id: Vec<u8>,
225    #[sensitive]
226    pub credential: Vec<u8>,
227    pub created_at: u64,
228    pub ciphersuite: u16,
229    #[sensitive]
230    pub public_key: Vec<u8>,
231    #[sensitive]
232    pub private_key: Vec<u8>,
233}
234
235/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
236#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
237#[zeroize(drop)]
238#[sensitive]
239pub struct StoredHpkePrivateKey {
240    pub sk: Vec<u8>,
241    pub pk: Vec<u8>,
242}
243
244/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
245#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
246#[zeroize(drop)]
247#[sensitive]
248pub struct StoredEncryptionKeyPair {
249    pub sk: Vec<u8>,
250    pub pk: Vec<u8>,
251}
252
253/// Entity representing a list of [StoredEncryptionKeyPair]
254#[derive(
255    core_crypto_macros::Debug,
256    Clone,
257    PartialEq,
258    Eq,
259    Zeroize,
260    core_crypto_macros::Entity,
261    core_crypto_macros::EntityNew,
262    serde::Serialize,
263    serde::Deserialize,
264)]
265#[zeroize(drop)]
266#[entity(collection_name = "mls_epoch_encryption_keypairs")]
267pub struct StoredEpochEncryptionKeypair {
268    #[entity(hex, column = "id_hex")]
269    #[id(hex, column = "id_hex")]
270    pub id: Vec<u8>,
271    #[sensitive]
272    pub keypairs: Vec<u8>,
273}
274
275/// Entity representing a persisted `SignatureKeyPair`
276#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
277#[zeroize(drop)]
278#[sensitive]
279pub struct StoredPskBundle {
280    pub psk_id: Vec<u8>,
281    pub psk: Vec<u8>,
282}
283
284/// Entity representing a persisted `KeyPackage`
285#[derive(
286    core_crypto_macros::Debug,
287    Clone,
288    PartialEq,
289    Eq,
290    Zeroize,
291    core_crypto_macros::Entity,
292    core_crypto_macros::EntityNew,
293    serde::Serialize,
294    serde::Deserialize,
295)]
296#[zeroize(drop)]
297#[entity(collection_name = "mls_keypackages")]
298pub struct StoredKeypackage {
299    #[entity(id, hex, column = "keypackage_ref_hex")]
300    #[id(hex, column = "keypackage_ref_hex")]
301    pub keypackage_ref: Vec<u8>,
302    #[sensitive]
303    pub keypackage: Vec<u8>,
304}
305
306/// Entity representing an enrollment instance used to fetch a x509 certificate and persisted when
307/// context switches and the memory it lives in is about to be erased
308#[derive(
309    core_crypto_macros::Debug,
310    Clone,
311    PartialEq,
312    Eq,
313    Zeroize,
314    core_crypto_macros::Entity,
315    core_crypto_macros::EntityNew,
316    serde::Serialize,
317    serde::Deserialize,
318)]
319#[zeroize(drop)]
320#[entity(collection_name = "e2ei_enrollment", no_upsert)]
321pub struct StoredE2eiEnrollment {
322    pub id: Vec<u8>,
323    pub content: Vec<u8>,
324}
325
326#[cfg(target_family = "wasm")]
327#[async_trait::async_trait(?Send)]
328pub trait UniqueEntity:
329    crate::entities::EntityBase<ConnectionType = crate::connection::KeystoreDatabaseConnection>
330    + serde::Serialize
331    + serde::de::DeserializeOwned
332where
333    Self: 'static,
334{
335    const ID: [u8; 1] = [0];
336
337    fn content(&self) -> &[u8];
338
339    fn set_content(&mut self, content: Vec<u8>);
340
341    async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Self> {
342        Ok(conn
343            .storage()
344            .get(Self::COLLECTION_NAME, &Self::ID)
345            .await?
346            .ok_or(CryptoKeystoreError::NotFound(Self::COLLECTION_NAME, "".to_string()))?)
347    }
348
349    async fn find_all(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Vec<Self>> {
350        match Self::find_unique(conn).await {
351            Ok(record) => Ok(vec![record]),
352            Err(CryptoKeystoreError::NotFound(..)) => Ok(vec![]),
353            Err(err) => Err(err),
354        }
355    }
356
357    async fn find_one(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Option<Self>> {
358        match Self::find_unique(conn).await {
359            Ok(record) => Ok(Some(record)),
360            Err(CryptoKeystoreError::NotFound(..)) => Ok(None),
361            Err(err) => Err(err),
362        }
363    }
364
365    async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<usize> {
366        conn.storage().count(Self::COLLECTION_NAME).await
367    }
368
369    async fn replace<'a>(&'a self, transaction: &TransactionWrapper<'a>) -> CryptoKeystoreResult<()> {
370        transaction.save(self.clone()).await?;
371        Ok(())
372    }
373}
374
375#[cfg(not(target_family = "wasm"))]
376#[async_trait::async_trait]
377pub trait UniqueEntity: EntityBase<ConnectionType = crate::connection::KeystoreDatabaseConnection> {
378    const ID: usize = 0;
379
380    fn new(content: Vec<u8>) -> Self;
381
382    async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Self> {
383        let mut conn = conn.conn().await;
384        let transaction = conn.transaction()?;
385        use rusqlite::OptionalExtension as _;
386
387        let maybe_content = transaction
388            .query_row(
389                &format!("SELECT content FROM {} WHERE id = ?", Self::COLLECTION_NAME),
390                [Self::ID],
391                |r| r.get::<_, Vec<u8>>(0),
392            )
393            .optional()?;
394
395        if let Some(content) = maybe_content {
396            Ok(Self::new(content))
397        } else {
398            Err(CryptoKeystoreError::NotFound(Self::COLLECTION_NAME, "".to_string()))
399        }
400    }
401
402    async fn find_all(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Vec<Self>> {
403        match Self::find_unique(conn).await {
404            Ok(record) => Ok(vec![record]),
405            Err(CryptoKeystoreError::NotFound(..)) => Ok(vec![]),
406            Err(err) => Err(err),
407        }
408    }
409
410    async fn find_one(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<Option<Self>> {
411        match Self::find_unique(conn).await {
412            Ok(record) => Ok(Some(record)),
413            Err(CryptoKeystoreError::NotFound(..)) => Ok(None),
414            Err(err) => Err(err),
415        }
416    }
417
418    async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<usize> {
419        let conn = conn.conn().await;
420        conn.query_row(&format!("SELECT COUNT(*) FROM {}", Self::COLLECTION_NAME), [], |r| {
421            r.get(0)
422        })
423        .map_err(Into::into)
424    }
425
426    fn content(&self) -> &[u8];
427
428    async fn replace(&self, transaction: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> {
429        use crate::connection::DatabaseConnection;
430        Self::ConnectionType::check_buffer_size(self.content().len())?;
431        let zb_content = rusqlite::blob::ZeroBlob(self.content().len() as i32);
432
433        use rusqlite::ToSql;
434        let params: [rusqlite::types::ToSqlOutput; 2] = [Self::ID.to_sql()?, zb_content.to_sql()?];
435
436        transaction.execute(
437            &format!(
438                "INSERT OR REPLACE INTO {} (id, content) VALUES (?, ?)",
439                Self::COLLECTION_NAME
440            ),
441            params,
442        )?;
443        let row_id = transaction.last_insert_rowid();
444
445        let mut blob = transaction.blob_open(rusqlite::MAIN_DB, Self::COLLECTION_NAME, "content", row_id, false)?;
446        use std::io::Write;
447        blob.write_all(self.content())?;
448        blob.close()?;
449
450        Ok(())
451    }
452}
453
454#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
455#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
456impl<T> crate::entities::EntityTransactionExt for T
457where
458    T: crate::entities::Entity<ConnectionType = crate::connection::KeystoreDatabaseConnection>
459        + UniqueEntity
460        + Send
461        + Sync,
462{
463    #[cfg(not(target_family = "wasm"))]
464    async fn save(&self, tx: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> {
465        self.replace(tx).await
466    }
467
468    #[cfg(target_family = "wasm")]
469    async fn save<'a>(&'a self, tx: &TransactionWrapper<'a>) -> CryptoKeystoreResult<()> {
470        self.replace(tx).await
471    }
472
473    #[cfg(not(target_family = "wasm"))]
474    async fn delete_fail_on_missing_id(
475        _: &TransactionWrapper<'_>,
476        _id: crate::entities::StringEntityId<'_>,
477    ) -> CryptoKeystoreResult<()> {
478        Err(CryptoKeystoreError::NotImplemented)
479    }
480
481    #[cfg(target_family = "wasm")]
482    async fn delete_fail_on_missing_id<'a>(
483        _: &TransactionWrapper<'a>,
484        _id: crate::entities::StringEntityId<'a>,
485    ) -> CryptoKeystoreResult<()> {
486        Err(CryptoKeystoreError::NotImplemented)
487    }
488}
489
490/// OIDC refresh token used in E2EI
491#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
492#[zeroize(drop)]
493pub struct E2eiRefreshToken {
494    pub content: Vec<u8>,
495}
496
497#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
498#[zeroize(drop)]
499pub struct E2eiAcmeCA {
500    pub content: Vec<u8>,
501}
502
503#[derive(
504    core_crypto_macros::Debug,
505    Clone,
506    PartialEq,
507    Eq,
508    Zeroize,
509    core_crypto_macros::Entity,
510    core_crypto_macros::EntityNew,
511    serde::Serialize,
512    serde::Deserialize,
513)]
514#[zeroize(drop)]
515pub struct E2eiIntermediateCert {
516    // key to identify the CA cert; Using a combination of SKI & AKI extensions concatenated like so is suitable:
517    // `SKI[+AKI]`
518    #[id]
519    #[entity(id)]
520    pub ski_aki_pair: String,
521    pub content: Vec<u8>,
522}
523
524#[derive(
525    core_crypto_macros::Debug,
526    Clone,
527    PartialEq,
528    Eq,
529    Zeroize,
530    core_crypto_macros::Entity,
531    core_crypto_macros::EntityNew,
532    serde::Serialize,
533    serde::Deserialize,
534)]
535#[zeroize(drop)]
536pub struct E2eiCrl {
537    #[id]
538    #[entity(id)]
539    pub distribution_point: String,
540    pub content: Vec<u8>,
541}