core_crypto_keystore/entities/
mls.rs

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