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;
24mod mls_provider;
25/// Proteus Abstraction
26#[cfg(feature = "proteus")]
27pub mod proteus;
28pub mod transaction_context;
29
30use std::sync::Arc;
31
32#[cfg(feature = "proteus")]
33use async_lock::Mutex;
34use async_lock::RwLock;
35pub use core_crypto_keystore::{ConnectionType, Database, DatabaseKey};
36#[cfg(test)]
37pub use core_crypto_macros::{dispotent, durable, idempotent};
38pub use openmls::{
39    group::{MlsGroup, MlsGroupConfig},
40    prelude::{
41        Ciphersuite as MlsCiphersuite, GroupEpoch, KeyPackageIn, MlsMessageIn, Node, SignatureScheme,
42        group_info::VerifiableGroupInfo,
43    },
44};
45use wire_e2e_identity::pki_env::PkiEnvironment;
46
47pub use crate::{
48    build_metadata::{BUILD_METADATA, BuildMetadata},
49    e2e_identity::{
50        E2eiEnrollment,
51        device_status::DeviceStatus,
52        identity::{WireIdentity, X509Identity},
53        types::{E2eiAcmeChallenge, E2eiAcmeDirectory, E2eiNewAcmeAuthz, E2eiNewAcmeOrder},
54    },
55    ephemeral::{HISTORY_CLIENT_ID_PREFIX, HistorySecret},
56    error::{
57        Error, InnermostErrorMessage, KeystoreError, LeafError, MlsError, MlsErrorKind, ProteusError, ProteusErrorKind,
58        RecursiveError, Result, ToRecursiveError,
59    },
60    mls::{
61        ciphersuite::Ciphersuite,
62        conversation::{
63            ConversationId, MlsConversation,
64            commit::MlsCommitBundle,
65            config::{MlsConversationConfiguration, MlsCustomConfiguration, MlsWirePolicy},
66            conversation_guard::decrypt::{MlsBufferedConversationDecryptMessage, MlsConversationDecryptMessage},
67            group_info::{GroupInfoPayload, MlsGroupInfoBundle, MlsGroupInfoEncryptionType, MlsRatchetTreeType},
68            proposal::MlsProposalBundle,
69            welcome::WelcomeBundle,
70        },
71        credential::{
72            Credential, CredentialRef, CredentialType, FindFilters as CredentialFindFilters, x509::CertificateBundle,
73        },
74        key_package::{Keypackage, KeypackageRef},
75        proposal::{MlsProposal, MlsProposalRef},
76        session::{
77            EpochObserver, HistoryObserver, Session,
78            id::{ClientId, ClientIdRef},
79            identifier::ClientIdentifier,
80            user_id::UserId,
81        },
82    },
83    mls_provider::{EntropySeed, MlsCryptoProvider, RawEntropySeed, RustCrypto},
84    transaction_context::{
85        e2e_identity::conversation_state::E2eiConversationState, key_package::KEYPACKAGE_DEFAULT_LIFETIME,
86    },
87};
88
89/// Response from the delivery service
90pub enum MlsTransportResponse {
91    /// The message was accepted by the delivery service
92    Success,
93    /// A client should have consumed all incoming messages before re-trying.
94    Retry,
95    /// The message was rejected by the delivery service and there's no recovery.
96    Abort {
97        /// Why did the delivery service reject the message?
98        reason: String,
99    },
100}
101
102/// An entity / data which has been packaged by the application to be encrypted
103/// and transmitted in an application message.
104#[derive(Debug, derive_more::From, derive_more::Deref, serde::Serialize, serde::Deserialize)]
105pub struct MlsTransportData(pub Vec<u8>);
106
107/// Client callbacks to allow communication with the delivery service.
108/// There are two different endpoints, one for messages and one for commit bundles.
109#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
110#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
111pub trait MlsTransport: std::fmt::Debug + Send + Sync {
112    /// Send a commit bundle to the corresponding endpoint.
113    async fn send_commit_bundle(&self, commit_bundle: MlsCommitBundle) -> Result<MlsTransportResponse>;
114    /// Send a message to the corresponding endpoint.
115    async fn send_message(&self, mls_message: Vec<u8>) -> Result<MlsTransportResponse>;
116
117    /// This function will be called before a history secret is sent to the mls transport to allow
118    /// the application to package it in a suitable transport container (json, protobuf, ...).
119    ///
120    /// The `secret` parameter contain the history client's secrets which will be sent over the mls transport.
121    ///
122    /// Returns the history secret packaged for transport
123    async fn prepare_for_transport(&self, secret: &HistorySecret) -> Result<MlsTransportData>;
124}
125
126/// This provider is mainly used for the initialization of the history client session, the only case where transport
127/// doesn't need to be implemented.
128#[derive(Debug, Default)]
129pub struct CoreCryptoTransportNotImplementedProvider();
130
131#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
132#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
133impl MlsTransport for CoreCryptoTransportNotImplementedProvider {
134    async fn send_commit_bundle(&self, _commit_bundle: MlsCommitBundle) -> crate::Result<MlsTransportResponse> {
135        Err(Error::MlsTransportNotProvided)
136    }
137
138    async fn send_message(&self, _mls_message: Vec<u8>) -> crate::Result<MlsTransportResponse> {
139        Err(Error::MlsTransportNotProvided)
140    }
141
142    async fn prepare_for_transport(&self, _secret: &HistorySecret) -> crate::Result<MlsTransportData> {
143        Err(Error::MlsTransportNotProvided)
144    }
145}
146
147/// Wrapper superstruct for both [mls::session::Session] and [proteus::ProteusCentral]
148///
149/// As [std::ops::Deref] is implemented, this struct is automatically dereferred to [mls::session::Session] apart from
150/// `proteus_*` calls
151///
152/// This is cheap to clone as all internal members have `Arc` wrappers or are `Copy`.
153#[derive(Debug, Clone)]
154pub struct CoreCrypto {
155    database: Database,
156    pki_environment: Arc<RwLock<Option<PkiEnvironment>>>,
157    mls: Arc<RwLock<Option<mls::session::Session<Database>>>>,
158    #[cfg(feature = "proteus")]
159    proteus: Arc<Mutex<Option<proteus::ProteusCentral>>>,
160    #[cfg(not(feature = "proteus"))]
161    #[allow(dead_code)]
162    proteus: (),
163}
164
165impl CoreCrypto {
166    /// Create an new CoreCrypto client without any initialized session.
167    pub fn new(database: Database) -> Self {
168        Self {
169            database,
170            pki_environment: Default::default(),
171            mls: Default::default(),
172            proteus: Default::default(),
173        }
174    }
175
176    /// Set the session's PKI Environment
177    pub async fn set_pki_environment(&self, pki_environment: Option<PkiEnvironment>) -> Result<()> {
178        if let Some(mls_session) = self.mls.write().await.as_mut() {
179            mls_session
180                .crypto_provider
181                .set_pki_environment_provider(pki_environment.as_ref().map(|p| p.mls_pki_env_provider()))
182                .await
183        }
184
185        let mut guard = self.pki_environment.write().await;
186        *guard = pki_environment;
187
188        Ok(())
189    }
190
191    /// Get the session's PKI Environment
192    pub async fn get_pki_environment(&self) -> Option<PkiEnvironment> {
193        self.pki_environment.read().await.clone()
194    }
195
196    /// Get the mls session if initialized
197    pub async fn mls_session(&self) -> Result<Session<Database>> {
198        if let Some(session) = self.mls.read().await.as_ref() {
199            return Ok(session.clone());
200        }
201        let err = Err(mls::session::Error::MlsNotInitialized);
202        err.map_err(RecursiveError::mls_client("Getting mls session"))?
203    }
204
205    /// Get the database
206    pub fn database(&self) -> Database {
207        self.database.clone()
208    }
209
210    /// Closes the database
211    /// indexdb connections must be closed explicitly while rusqlite implements drop which suffices.
212    pub async fn close(&self) -> Result<()> {
213        self.database
214            .close()
215            .await
216            .map_err(crate::KeystoreError::wrap("Closing database"))?;
217
218        if let Some(pki_env) = self.get_pki_environment().await {
219            pki_env
220                .database()
221                .close()
222                .await
223                .map_err(crate::KeystoreError::wrap("Closing PKI environment database"))?;
224        }
225
226        Ok(())
227    }
228}