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