core_crypto/
lib.rs

1//! Core Crypto is a wrapper on top of OpenMLS aimed to provide an ergonomic API for usage in web
2//! through Web Assembly and in mobile devices through FFI.
3//!
4//! The goal is provide a easier and less verbose API to create, manage and interact with MLS
5//! groups.
6#![doc = include_str!(env!("STRIPPED_README_PATH"))]
7#![cfg_attr(not(test), deny(missing_docs))]
8#![allow(clippy::single_component_path_imports)]
9
10#[cfg(test)]
11#[macro_use]
12pub mod test_utils;
13// both imports above have to be defined at the beginning of the crate for rstest to work
14
15mod build_metadata;
16mod ephemeral;
17mod error;
18mod group_store;
19
20/// re-export [rusty-jwt-tools](https://github.com/wireapp/rusty-jwt-tools) API
21pub mod e2e_identity;
22/// MLS Abstraction
23pub mod mls;
24/// Proteus Abstraction
25#[cfg(feature = "proteus")]
26pub mod proteus;
27pub mod transaction_context;
28
29use std::sync::Arc;
30
31#[cfg(feature = "proteus")]
32use async_lock::Mutex;
33use async_lock::RwLock;
34pub use core_crypto_keystore::{ConnectionType, Database, DatabaseKey};
35#[cfg(test)]
36pub use core_crypto_macros::{dispotent, durable, idempotent};
37pub use mls_crypto_provider::{EntropySeed, MlsCryptoProvider, RawEntropySeed};
38pub use openmls::{
39    group::{MlsGroup, MlsGroupConfig},
40    prelude::{
41        Ciphersuite as MlsCiphersuite, GroupEpoch, KeyPackageIn, MlsMessageIn, Node, SignatureScheme,
42        group_info::VerifiableGroupInfo,
43    },
44};
45
46pub use crate::{
47    build_metadata::{BUILD_METADATA, BuildMetadata},
48    e2e_identity::{
49        E2eiEnrollment,
50        device_status::DeviceStatus,
51        identity::{WireIdentity, X509Identity},
52        types::{E2eiAcmeChallenge, E2eiAcmeDirectory, E2eiNewAcmeAuthz, E2eiNewAcmeOrder},
53    },
54    ephemeral::{HISTORY_CLIENT_ID_PREFIX, HistorySecret},
55    error::{
56        Error, InnermostErrorMessage, KeystoreError, LeafError, MlsError, MlsErrorKind, ProteusError, ProteusErrorKind,
57        RecursiveError, Result, ToRecursiveError,
58    },
59    mls::{
60        ciphersuite::Ciphersuite,
61        conversation::{
62            ConversationId, MlsConversation,
63            commit::MlsCommitBundle,
64            config::{MlsConversationConfiguration, MlsCustomConfiguration, MlsWirePolicy},
65            conversation_guard::decrypt::{MlsBufferedConversationDecryptMessage, MlsConversationDecryptMessage},
66            group_info::{GroupInfoPayload, MlsGroupInfoBundle, MlsGroupInfoEncryptionType, MlsRatchetTreeType},
67            proposal::MlsProposalBundle,
68            welcome::WelcomeBundle,
69        },
70        credential::{
71            Credential, CredentialRef, CredentialType, FindFilters as CredentialFindFilters, x509::CertificateBundle,
72        },
73        key_package::{Keypackage, KeypackageRef},
74        proposal::{MlsProposal, MlsProposalRef},
75        session::{
76            EpochObserver, HistoryObserver, Session,
77            id::{ClientId, ClientIdRef},
78            identifier::ClientIdentifier,
79            key_package::{INITIAL_KEYING_MATERIAL_COUNT, KEYPACKAGE_DEFAULT_LIFETIME},
80            user_id::UserId,
81        },
82    },
83    transaction_context::e2e_identity::conversation_state::E2eiConversationState,
84};
85
86/// Response from the delivery service
87pub enum MlsTransportResponse {
88    /// The message was accepted by the delivery service
89    Success,
90    /// A client should have consumed all incoming messages before re-trying.
91    Retry,
92    /// The message was rejected by the delivery service and there's no recovery.
93    Abort {
94        /// Why did the delivery service reject the message?
95        reason: String,
96    },
97}
98
99/// An entity / data which has been packaged by the application to be encrypted
100/// and transmitted in an application message.
101#[derive(Debug, derive_more::From, derive_more::Deref, serde::Serialize, serde::Deserialize)]
102pub struct MlsTransportData(pub Vec<u8>);
103
104/// Client callbacks to allow communication with the delivery service.
105/// There are two different endpoints, one for messages and one for commit bundles.
106#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
107#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
108pub trait MlsTransport: std::fmt::Debug + Send + Sync {
109    /// Send a commit bundle to the corresponding endpoint.
110    async fn send_commit_bundle(&self, commit_bundle: MlsCommitBundle) -> Result<MlsTransportResponse>;
111    /// Send a message to the corresponding endpoint.
112    async fn send_message(&self, mls_message: Vec<u8>) -> Result<MlsTransportResponse>;
113
114    /// This function will be called before a history secret is sent to the mls transport to allow
115    /// the application to package it in a suitable transport container (json, protobuf, ...).
116    ///
117    /// The `secret` parameter contain the history client's secrets which will be sent over the mls transport.
118    ///
119    /// Returns the history secret packaged for transport
120    async fn prepare_for_transport(&self, secret: &HistorySecret) -> Result<MlsTransportData>;
121}
122
123/// This provider is mainly used for the initialization of the history client session, the only case where transport
124/// doesn't need to be implemented.
125#[derive(Debug, Default)]
126pub struct CoreCryptoTransportNotImplementedProvider();
127
128#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
129#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
130impl MlsTransport for CoreCryptoTransportNotImplementedProvider {
131    async fn send_commit_bundle(&self, _commit_bundle: MlsCommitBundle) -> crate::Result<MlsTransportResponse> {
132        Err(Error::MlsTransportNotProvided)
133    }
134
135    async fn send_message(&self, _mls_message: Vec<u8>) -> crate::Result<MlsTransportResponse> {
136        Err(Error::MlsTransportNotProvided)
137    }
138
139    async fn prepare_for_transport(&self, _secret: &HistorySecret) -> crate::Result<MlsTransportData> {
140        Err(Error::MlsTransportNotProvided)
141    }
142}
143
144/// Wrapper superstruct for both [mls::session::Session] and [proteus::ProteusCentral]
145///
146/// As [std::ops::Deref] is implemented, this struct is automatically dereferred to [mls::session::Session] apart from
147/// `proteus_*` calls
148///
149/// This is cheap to clone as all internal members have `Arc` wrappers or are `Copy`.
150#[derive(Debug, Clone)]
151pub struct CoreCrypto {
152    database: Database,
153    mls: Arc<RwLock<Option<mls::session::Session>>>,
154    #[cfg(feature = "proteus")]
155    proteus: Arc<Mutex<Option<proteus::ProteusCentral>>>,
156    #[cfg(not(feature = "proteus"))]
157    #[allow(dead_code)]
158    proteus: (),
159}
160
161impl CoreCrypto {
162    /// Create an new CoreCrypto client without any initialized session.
163    pub fn new(database: Database) -> Self {
164        Self {
165            database,
166            mls: Default::default(),
167            proteus: Default::default(),
168        }
169    }
170
171    /// Get the mls session if initialized
172    pub async fn mls_session(&self) -> Result<Session> {
173        if let Some(session) = self.mls.read().await.as_ref() {
174            return Ok(session.clone());
175        }
176        let err = Err(mls::session::Error::MlsNotInitialized);
177        err.map_err(RecursiveError::mls_client("Getting mls session"))?
178    }
179
180    /// Get the database
181    pub fn database(&self) -> Database {
182        self.database.clone()
183    }
184
185    /// Closes the database
186    /// indexdb connections must be closed explicitly while rusqlite implements drop which suffices.
187    pub async fn close(&self) -> Result<()> {
188        self.database
189            .close()
190            .await
191            .map_err(crate::KeystoreError::wrap("Closing database"))?;
192        Ok(())
193    }
194}