core_crypto_keystore/entities/
mls.rs

1use zeroize::Zeroize;
2
3use crate::{
4    CryptoKeystoreResult, Sha256Hash,
5    traits::{EntityBase, EntityGetBorrowed as _, KeyType, OwnedKeyType, PrimaryKey, SearchableEntity as _},
6};
7
8/// This type exists so that we can efficiently search for the children of a given group.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::From, derive_more::Into, derive_more::AsRef)]
10pub struct ParentGroupId<'a>(&'a [u8]);
11
12impl<'a> KeyType for ParentGroupId<'a> {
13    fn bytes(&self) -> std::borrow::Cow<'_, [u8]> {
14        self.0.into()
15    }
16}
17
18/// Entity representing a persisted `MlsGroup`
19#[derive(
20    core_crypto_macros::Debug,
21    Clone,
22    PartialEq,
23    Eq,
24    Zeroize,
25    core_crypto_macros::Entity,
26    serde::Serialize,
27    serde::Deserialize,
28)]
29#[zeroize(drop)]
30#[entity(collection_name = "mls_groups")]
31#[sensitive]
32pub struct PersistedMlsGroup {
33    pub id: Vec<u8>,
34    pub state: Vec<u8>,
35    #[entity(unencrypted_wasm)]
36    pub parent_id: Option<Vec<u8>>,
37}
38
39impl PersistedMlsGroup {
40    /// Get the parent group of this group.
41    pub async fn parent_group(
42        &self,
43        conn: &mut <Self as EntityBase>::ConnectionType,
44    ) -> CryptoKeystoreResult<Option<Self>> {
45        let Some(parent_id) = self.parent_id.as_deref() else {
46            return Ok(None);
47        };
48
49        Self::get_borrowed(conn, parent_id).await
50    }
51
52    /// Get all children of this group.
53    pub async fn child_groups(
54        &self,
55        conn: &mut <Self as EntityBase>::ConnectionType,
56    ) -> CryptoKeystoreResult<Vec<Self>> {
57        let parent_id = self.id.as_slice();
58        Self::find_all_matching(conn, &parent_id.into()).await
59    }
60}
61
62/// Entity representing a temporarily persisted `MlsGroup`
63#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
64#[zeroize(drop)]
65pub struct PersistedMlsPendingGroup {
66    #[sensitive]
67    pub id: Vec<u8>,
68    #[sensitive]
69    pub state: Vec<u8>,
70    #[sensitive]
71    pub parent_id: Option<Vec<u8>>,
72    pub custom_configuration: Vec<u8>,
73}
74
75/// Typesafe reference to a conversation id.
76///
77/// [`MlsPendingMessage`]s have no distinct primary key; they must always be accessed via
78/// collective accessors. This type makes that possible.
79#[derive(
80    Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, derive_more::AsRef, derive_more::Deref, derive_more::From,
81)]
82pub struct ConversationId<'a>(&'a [u8]);
83
84impl<'a> KeyType for ConversationId<'a> {
85    fn bytes(&self) -> std::borrow::Cow<'_, [u8]> {
86        self.0.into()
87    }
88}
89
90/// [`MlsPendingMessage`]s have no distinct primary key;
91/// they must always be accessed via the [`SearchableEntity`][crate::traits::SearchableEntity] and
92/// [`DeletableBySearchKey`][crate::traits::DeletableBySearchKey] traits.
93///
94/// However the keystore's support of internal transactions demands a primary key:
95/// ultimately that structure boils down to `Map<CollectionName, Map<PrimaryKey, Entity>>`, so anything other
96/// than a full primary key just breaks things.
97///
98/// We use `xxhash3` as a fast hash implementation, and take 128 bits of hash to ensure
99/// that the chance of a collision is effectively 0.
100pub struct MlsPendingMessagePrimaryKey(u128);
101
102impl From<&MlsPendingMessage> for MlsPendingMessagePrimaryKey {
103    fn from(value: &MlsPendingMessage) -> Self {
104        let mut hasher = twox_hash::xxhash3_128::Hasher::new();
105        hasher.write(&value.foreign_id);
106        hasher.write(&value.message);
107        Self(hasher.finish_128())
108    }
109}
110
111impl KeyType for MlsPendingMessagePrimaryKey {
112    fn bytes(&self) -> std::borrow::Cow<'_, [u8]> {
113        self.0.to_be_bytes().as_slice().to_owned().into()
114    }
115}
116
117impl OwnedKeyType for MlsPendingMessagePrimaryKey {
118    fn from_bytes(bytes: &[u8]) -> Option<Self> {
119        let array = bytes.try_into().ok()?;
120        Some(Self(u128::from_be_bytes(array)))
121    }
122}
123
124/// Entity representing a buffered message
125#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
126#[zeroize(drop)]
127pub struct MlsPendingMessage {
128    #[sensitive]
129    pub foreign_id: Vec<u8>,
130    pub message: Vec<u8>,
131}
132
133impl PrimaryKey for MlsPendingMessage {
134    type PrimaryKey = MlsPendingMessagePrimaryKey;
135    fn primary_key(&self) -> Self::PrimaryKey {
136        self.into()
137    }
138}
139
140/// Entity representing a buffered commit.
141///
142/// There should always exist either 0 or 1 of these in the store per conversation.
143/// Commits are buffered if not all proposals they reference have yet been received.
144///
145/// We don't automatically zeroize on drop because the commit data is still encrypted at this point;
146/// it is not risky to leave it in memory.
147#[derive(
148    core_crypto_macros::Debug,
149    Clone,
150    PartialEq,
151    Eq,
152    Zeroize,
153    core_crypto_macros::Entity,
154    serde::Serialize,
155    serde::Deserialize,
156)]
157#[entity(collection_name = "mls_buffered_commits")]
158pub struct StoredBufferedCommit {
159    #[entity(id)]
160    #[sensitive]
161    conversation_id: Vec<u8>,
162    commit_data: Vec<u8>,
163}
164
165impl StoredBufferedCommit {
166    /// Create a new `Self` from conversation id and the commit data.
167    pub fn new(conversation_id: Vec<u8>, commit_data: Vec<u8>) -> Self {
168        Self {
169            conversation_id,
170            commit_data,
171        }
172    }
173
174    pub fn conversation_id(&self) -> &[u8] {
175        &self.conversation_id
176    }
177
178    pub fn commit_data(&self) -> &[u8] {
179        &self.commit_data
180    }
181
182    pub fn into_commit_data(self) -> Vec<u8> {
183        self.commit_data
184    }
185}
186
187/// This type exists so that we can efficiently search for credentials by a variety of metrics at the database level.
188///
189/// This includes some but not all of the fields in `core_crypto::CredentialFindFilters`: those that are actually stored
190/// in the database, and do not require deserializing the `credential` field.
191#[derive(Debug, Default, Clone, Copy, serde::Serialize)]
192pub struct CredentialFindFilters<'a> {
193    /// Hash of public key to search for.
194    pub hash: Option<Sha256Hash>,
195    /// Public key to search for
196    pub public_key: Option<&'a [u8]>,
197    /// Session / Client id to search for
198    pub session_id: Option<&'a [u8]>,
199    /// Ciphersuite to search for
200    pub ciphersuite: Option<u16>,
201    /// unix timestamp (seconds) of point of earliest validity to search for
202    pub earliest_validity: Option<u64>,
203}
204
205impl<'a> KeyType for CredentialFindFilters<'a> {
206    fn bytes(&self) -> std::borrow::Cow<'_, [u8]> {
207        postcard::to_stdvec(self)
208            .expect("serializing these filters cannot fail")
209            .into()
210    }
211}
212
213/// Entity representing a persisted `Credential`
214#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
215#[zeroize(drop)]
216pub struct StoredCredential {
217    /// Note: this is not a unique identifier, but the session id this credential belongs to.
218    #[sensitive]
219    pub session_id: Vec<u8>,
220    #[sensitive]
221    pub credential: Vec<u8>,
222    pub created_at: u64,
223    pub ciphersuite: u16,
224    #[sensitive]
225    pub public_key: Vec<u8>,
226    #[sensitive]
227    pub private_key: Vec<u8>,
228}
229
230/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
231#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
232#[zeroize(drop)]
233#[sensitive]
234pub struct StoredHpkePrivateKey {
235    pub sk: Vec<u8>,
236    pub pk: Vec<u8>,
237}
238
239/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of)
240#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
241#[zeroize(drop)]
242#[sensitive]
243pub struct StoredEncryptionKeyPair {
244    pub sk: Vec<u8>,
245    pub pk: Vec<u8>,
246}
247
248/// Entity representing a list of [StoredEncryptionKeyPair]
249#[derive(
250    core_crypto_macros::Debug,
251    Clone,
252    PartialEq,
253    Eq,
254    Zeroize,
255    core_crypto_macros::Entity,
256    serde::Serialize,
257    serde::Deserialize,
258)]
259#[zeroize(drop)]
260#[entity(collection_name = "mls_epoch_encryption_keypairs")]
261pub struct StoredEpochEncryptionKeypair {
262    pub id: Vec<u8>,
263    #[sensitive]
264    pub keypairs: Vec<u8>,
265}
266
267/// Entity representing a persisted `SignatureKeyPair`
268#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
269#[zeroize(drop)]
270#[sensitive]
271pub struct StoredPskBundle {
272    pub psk_id: Vec<u8>,
273    pub psk: Vec<u8>,
274}
275
276/// Entity representing a persisted `KeyPackage`
277#[derive(
278    core_crypto_macros::Debug,
279    Clone,
280    PartialEq,
281    Eq,
282    Zeroize,
283    core_crypto_macros::Entity,
284    serde::Serialize,
285    serde::Deserialize,
286)]
287#[zeroize(drop)]
288#[entity(collection_name = "mls_keypackages")]
289pub struct StoredKeypackage {
290    #[entity(id)]
291    pub keypackage_ref: Vec<u8>,
292    #[sensitive]
293    pub keypackage: Vec<u8>,
294}
295
296/// Entity representing an enrollment instance used to fetch a x509 certificate and persisted when
297/// context switches and the memory it lives in is about to be erased
298#[derive(
299    core_crypto_macros::Debug,
300    Clone,
301    PartialEq,
302    Eq,
303    Zeroize,
304    core_crypto_macros::Entity,
305    serde::Serialize,
306    serde::Deserialize,
307)]
308#[zeroize(drop)]
309#[entity(collection_name = "e2ei_enrollment", no_upsert)]
310pub struct StoredE2eiEnrollment {
311    pub id: Vec<u8>,
312    pub content: Vec<u8>,
313}
314
315/// OIDC refresh token used in E2EI
316#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
317#[zeroize(drop)]
318pub struct E2eiRefreshToken {
319    pub content: Vec<u8>,
320}
321
322#[derive(core_crypto_macros::Debug, Clone, PartialEq, Eq, Zeroize, serde::Serialize, serde::Deserialize)]
323#[zeroize(drop)]
324pub struct E2eiAcmeCA {
325    pub content: Vec<u8>,
326}
327
328#[derive(
329    core_crypto_macros::Debug,
330    Clone,
331    PartialEq,
332    Eq,
333    Zeroize,
334    core_crypto_macros::Entity,
335    serde::Serialize,
336    serde::Deserialize,
337)]
338#[zeroize(drop)]
339pub struct E2eiIntermediateCert {
340    // key to identify the CA cert; Using a combination of SKI & AKI extensions concatenated like so is suitable:
341    // `SKI[+AKI]`
342    #[entity(id)]
343    pub ski_aki_pair: String,
344    pub content: Vec<u8>,
345}
346
347#[derive(
348    core_crypto_macros::Debug,
349    Clone,
350    PartialEq,
351    Eq,
352    Zeroize,
353    core_crypto_macros::Entity,
354    serde::Serialize,
355    serde::Deserialize,
356)]
357#[zeroize(drop)]
358pub struct E2eiCrl {
359    #[entity(id)]
360    pub distribution_point: String,
361    pub content: Vec<u8>,
362}