Skip to main content

core_crypto_keystore/
mls.rs

1use openmls::prelude::Ciphersuite;
2use openmls_basic_credential::SignatureKeyPair;
3use openmls_traits::key_store::{MlsEntity, MlsEntityId};
4
5use crate::{
6    CryptoKeystoreError, CryptoKeystoreResult, Database, Sha256Hash,
7    entities::{
8        PersistedMlsGroup, PersistedMlsPendingGroup, StoredCredential, StoredEncryptionKeyPair,
9        StoredEpochEncryptionKeypair, StoredHpkePrivateKey, StoredKeypackage, StoredPskBundle,
10    },
11    traits::{BorrowPrimaryKey, Entity, EntityDatabaseMutation, EntityDeleteBorrowed, FetchFromDatabase as _},
12};
13
14/// convenience methods to modify the in-flight transaction
15impl Database {
16    async fn save<E>(&self, entity: E) -> CryptoKeystoreResult<E::AutoGeneratedFields>
17    where
18        E: Entity + EntityDatabaseMutation + Send + Sync,
19    {
20        self.with_transaction(async |tx| tx.save(entity).await).await
21    }
22
23    async fn remove_borrowed<E>(&self, id: &E::BorrowedPrimaryKey) -> CryptoKeystoreResult<()>
24    where
25        E: Entity + EntityDatabaseMutation + BorrowPrimaryKey + EntityDeleteBorrowed,
26    {
27        self.with_transaction(async |tx| tx.remove_borrowed::<E>(id).await)
28            .await
29    }
30}
31
32impl Database {
33    /// Fetches Keypackages
34    ///
35    /// # Arguments
36    /// * `count` - amount of entries to be returned
37    ///
38    /// # Errors
39    /// Any common error that can happen during a database connection. IoError being a common error
40    /// for example.
41    pub async fn mls_fetch_key_packages<V: MlsEntity>(&self, count: u32) -> CryptoKeystoreResult<Vec<V>> {
42        let keypackages = StoredKeypackage::load_all(&*self.conn().await)?;
43        Ok(keypackages
44            .into_iter()
45            .filter_map(|kpb| postcard::from_bytes(&kpb.keypackage).ok())
46            .take(count as _)
47            .collect())
48    }
49
50    /// Checks if the given MLS group id exists in the keystore
51    /// Note: in case of any error, this will return false
52    ///
53    /// # Arguments
54    /// * `group_id` - group/conversation id
55    pub async fn mls_group_exists(&self, group_id: impl AsRef<[u8]> + Send) -> bool {
56        matches!(
57            self.get_borrowed::<PersistedMlsGroup>(group_id.as_ref()).await,
58            Ok(Some(_))
59        )
60    }
61
62    /// Persists a `MlsGroup`
63    ///
64    /// # Arguments
65    /// * `group_id` - group/conversation id
66    /// * `state` - the group state
67    ///
68    /// # Errors
69    /// Any common error that can happen during a database connection. IoError being a common error
70    /// for example.
71    pub async fn mls_group_persist(
72        &self,
73        group_id: impl AsRef<[u8]> + Send,
74        state: &[u8],
75        sender_nonce: u32,
76        parent_group_id: Option<&[u8]>,
77    ) -> CryptoKeystoreResult<()> {
78        self.save(PersistedMlsGroup {
79            id: group_id.as_ref().to_owned(),
80            state: state.into(),
81            sender_nonce,
82            parent_id: parent_group_id.map(Into::into),
83        })
84        .await?;
85        Ok(())
86    }
87
88    /// Loads `MlsGroups` from the database. It will be returned as a `HashMap` where the key is
89    /// the group/conversation id and the value the group state
90    ///
91    /// # Errors
92    /// Any common error that can happen during a database connection. IoError being a common error
93    /// for example.
94    pub async fn mls_groups_restore(
95        &self,
96    ) -> CryptoKeystoreResult<std::collections::HashMap<Vec<u8>, (Option<Vec<u8>>, Vec<u8>)>> {
97        let groups = PersistedMlsGroup::load_all(&*self.conn().await)?;
98        Ok(groups
99            .into_iter()
100            .map(|mut group| {
101                let id = std::mem::take(&mut group.id);
102                let parent_id = std::mem::take(&mut group.parent_id);
103                let state = std::mem::take(&mut group.state);
104                (id, (parent_id, state))
105            })
106            .collect())
107    }
108
109    /// Deletes `MlsGroups` from the database.
110    /// # Errors
111    /// Any common error that can happen during a database connection. IoError being a common error
112    /// for example.
113    pub async fn mls_group_delete(&self, group_id: impl AsRef<[u8]> + Send) -> CryptoKeystoreResult<()> {
114        self.remove_borrowed::<PersistedMlsGroup>(group_id.as_ref()).await?;
115        Ok(())
116    }
117
118    /// Saves a `MlsGroup` in a temporary table (typically used in scenarios where the group cannot
119    /// be committed until the backend acknowledges it, like external commits)
120    ///
121    /// # Arguments
122    /// * `group_id` - group/conversation id
123    /// * `mls_group` - the group/conversation state
124    /// * `custom_configuration` - local group configuration
125    ///
126    /// # Errors
127    /// Any common error that can happen during a database connection. IoError being a common error
128    /// for example.
129    pub async fn mls_pending_groups_save(
130        &self,
131        group_id: impl AsRef<[u8]> + Send,
132        mls_group: &[u8],
133        custom_configuration: &[u8],
134        parent_group_id: Option<&[u8]>,
135    ) -> CryptoKeystoreResult<()> {
136        self.save(PersistedMlsPendingGroup {
137            id: group_id.as_ref().to_owned(),
138            state: mls_group.into(),
139            custom_configuration: custom_configuration.into(),
140            parent_id: parent_group_id.map(Into::into),
141        })
142        .await?;
143        Ok(())
144    }
145
146    /// Loads a temporary `MlsGroup` and its configuration from the database
147    ///
148    /// # Arguments
149    /// * `id` - group/conversation id
150    ///
151    /// # Errors
152    /// Any common error that can happen during a database connection. IoError being a common error
153    /// for example.
154    pub async fn mls_pending_groups_load(
155        &self,
156        group_id: impl AsRef<[u8]> + Send,
157    ) -> CryptoKeystoreResult<Option<(Vec<u8>, Vec<u8>)>> {
158        let optional = self.get_borrowed::<PersistedMlsPendingGroup>(group_id.as_ref()).await?;
159        Ok(optional.map(|pending_group| (pending_group.state.clone(), pending_group.custom_configuration.clone())))
160    }
161
162    /// Deletes a temporary `MlsGroup` from the database
163    ///
164    /// # Arguments
165    /// * `id` - group/conversation id
166    ///
167    /// # Errors
168    /// Any common error that can happen during a database connection. IoError being a common error
169    /// for example.
170    pub async fn mls_pending_groups_delete(&self, group_id: impl AsRef<[u8]> + Send) -> CryptoKeystoreResult<()> {
171        self.remove_borrowed::<PersistedMlsPendingGroup>(group_id.as_ref())
172            .await?;
173        Ok(())
174    }
175}
176
177#[inline(always)]
178pub fn deser<T: MlsEntity>(bytes: &[u8]) -> Result<T, CryptoKeystoreError> {
179    Ok(postcard::from_bytes(bytes)?)
180}
181
182#[inline(always)]
183pub fn ser<T: MlsEntity>(value: &T) -> Result<Vec<u8>, CryptoKeystoreError> {
184    Ok(postcard::to_stdvec(value)?)
185}
186
187#[cfg_attr(target_os = "unknown", async_trait::async_trait(?Send))]
188#[cfg_attr(not(target_os = "unknown"), async_trait::async_trait)]
189impl openmls_traits::key_store::OpenMlsKeyStore for Database {
190    type Error = CryptoKeystoreError;
191
192    async fn store<V: MlsEntity + Sync>(&self, id: &[u8], value: &V) -> Result<(), Self::Error>
193    where
194        Self: Sized,
195    {
196        if id.is_empty() {
197            return Err(CryptoKeystoreError::MlsKeyStoreError(
198                "The provided key is empty".into(),
199            ));
200        }
201
202        let data = ser(value)?;
203
204        match V::ID {
205            MlsEntityId::GroupState => {
206                return Err(CryptoKeystoreError::IncorrectApiUsage(
207                    "Groups must not be saved using OpenMLS's APIs. You should use the keystore's provided methods",
208                ));
209            }
210            MlsEntityId::SignatureKeyPair => {
211                return Err(CryptoKeystoreError::IncorrectApiUsage(
212                    "Signature keys must not be saved using OpenMLS's APIs. Save a credential via the keystore API
213                    instead.",
214                ));
215            }
216            MlsEntityId::KeyPackage => {
217                let kp = StoredKeypackage {
218                    keypackage_ref: id.into(),
219                    keypackage: data,
220                };
221                self.save(kp).await?;
222            }
223            MlsEntityId::HpkePrivateKey => {
224                let kp = StoredHpkePrivateKey {
225                    pk: id.into(),
226                    sk: data,
227                };
228                self.save(kp).await?;
229            }
230            MlsEntityId::PskBundle => {
231                let kp = StoredPskBundle {
232                    psk_id: id.into(),
233                    psk: data,
234                };
235                self.save(kp).await?;
236            }
237            MlsEntityId::EncryptionKeyPair => {
238                let kp = StoredEncryptionKeyPair {
239                    pk: id.into(),
240                    sk: data,
241                };
242                self.save(kp).await?;
243            }
244            MlsEntityId::EpochEncryptionKeyPair => {
245                let kp = StoredEpochEncryptionKeypair {
246                    id: id.into(),
247                    keypairs: data,
248                };
249                self.save(kp).await?;
250            }
251        }
252
253        Ok(())
254    }
255
256    async fn read<V: MlsEntity>(&self, id: &[u8]) -> Option<V>
257    where
258        Self: Sized,
259    {
260        if id.is_empty() {
261            return None;
262        }
263
264        match V::ID {
265            MlsEntityId::GroupState => {
266                let v = self.get_borrowed::<PersistedMlsGroup>(id).await.ok().flatten()?;
267                deser(&v.state).ok()
268            }
269            MlsEntityId::SignatureKeyPair => {
270                let conn = &*self.conn().await;
271                let hash = Sha256Hash::from_existing_hash(id).ok()?;
272                let stored_credential = StoredCredential::get(conn, &hash).ok().flatten()?;
273                let ciphersuite = Ciphersuite::try_from(stored_credential.ciphersuite).ok()?;
274                let signature_scheme = ciphersuite.signature_algorithm();
275
276                let mls_keypair = SignatureKeyPair::from_raw(
277                    signature_scheme,
278                    stored_credential.private_key.to_vec(),
279                    stored_credential.public_key.to_vec(),
280                );
281
282                // In a well designed interface, something like this should not be necessary. However, we don't have
283                // a well-designed interface.
284                let data = ser(&mls_keypair).ok()?;
285                deser(&data).ok()
286            }
287            MlsEntityId::KeyPackage => {
288                let v = self.get_borrowed::<StoredKeypackage>(id).await.ok().flatten()?;
289                deser(&v.keypackage).ok()
290            }
291            MlsEntityId::HpkePrivateKey => {
292                let v = self.get_borrowed::<StoredHpkePrivateKey>(id).await.ok().flatten()?;
293                deser(&v.sk).ok()
294            }
295            MlsEntityId::PskBundle => {
296                let v = self.get_borrowed::<StoredPskBundle>(id).await.ok().flatten()?;
297                deser(&v.psk).ok()
298            }
299            MlsEntityId::EncryptionKeyPair => {
300                let v = self.get_borrowed::<StoredEncryptionKeyPair>(id).await.ok().flatten()?;
301                deser(&v.sk).ok()
302            }
303            MlsEntityId::EpochEncryptionKeyPair => {
304                let v = self
305                    .get_borrowed::<StoredEpochEncryptionKeypair>(id)
306                    .await
307                    .ok()
308                    .flatten()?;
309                deser(&v.keypairs).ok()
310            }
311        }
312    }
313
314    async fn delete<V: MlsEntity>(&self, id: &[u8]) -> Result<(), Self::Error> {
315        match V::ID {
316            MlsEntityId::GroupState => self.remove_borrowed::<PersistedMlsGroup>(id).await?,
317            MlsEntityId::SignatureKeyPair => unimplemented!(
318                "Deleting a signature key pair should not be done through this API, any keypair should be deleted via
319                deleting a credential."
320            ),
321            MlsEntityId::HpkePrivateKey => self.remove_borrowed::<StoredHpkePrivateKey>(id).await?,
322            MlsEntityId::KeyPackage => self.remove_borrowed::<StoredKeypackage>(id).await?,
323            MlsEntityId::PskBundle => self.remove_borrowed::<StoredPskBundle>(id).await?,
324            MlsEntityId::EncryptionKeyPair => self.remove_borrowed::<StoredEncryptionKeyPair>(id).await?,
325            MlsEntityId::EpochEncryptionKeyPair => self.remove_borrowed::<StoredEpochEncryptionKeypair>(id).await?,
326        }
327
328        Ok(())
329    }
330}