Skip to main content

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
9#[cfg(test)]
10#[macro_use]
11pub mod test_utils;
12// both imports above have to be defined at the beginning of the crate for rstest to work
13
14mod build_metadata;
15mod bytes_wrapper;
16mod ephemeral;
17mod error;
18mod identity;
19mod immutable_database;
20
21/// MLS Abstraction
22pub mod mls;
23mod mls_provider;
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(crate) use bytes_wrapper::bytes_wrapper;
35pub use core_crypto_keystore::{Database, DatabaseKey};
36#[cfg(test)]
37pub use core_crypto_macros::{dispotent, durable, idempotent};
38pub(crate) use immutable_database::ImmutableDatabase;
39pub use openmls::{
40    group::{MlsGroup, MlsGroupConfig},
41    prelude::{
42        Ciphersuite as MlsCiphersuite, GroupEpoch, KeyPackageIn, MlsMessageIn, MlsMessageInBody, Node, SignatureScheme,
43        group_info::VerifiableGroupInfo,
44    },
45};
46use wire_e2e_identity::pki_env::PkiEnvironment;
47
48pub use crate::{
49    build_metadata::{BUILD_METADATA, BuildMetadata},
50    ephemeral::{HISTORY_CLIENT_ID_PREFIX, HistorySecret},
51    error::{
52        Error, InnermostErrorMessage, KeystoreError, OpenMlsError, OpenMlsErrorKind, ProteusError, ProteusErrorKind,
53        RecursiveError, Result, TlsCodecError, ToRecursiveError,
54    },
55    identity::{WireIdentity, X509Identity},
56    mls::{
57        ExternalSender, UnknownCipherSuite,
58        cipher_suite::CipherSuite,
59        conversation::{
60            BufferedCommit, BufferedDecryptedMessage, Commit, CommitBundle, ConversationConfiguration, ConversationId,
61            DecryptedBytes, DecryptedMessage, GroupInfoBundle, GroupInfoEncryptionType, GroupInfoPayload, Proposal,
62            RatchetTreeType, WirePolicy,
63        },
64        credential::{
65            Credential, CredentialRef, CredentialType, FindFilters as CredentialFindFilters, x509::CertificateBundle,
66        },
67        key_package::{Keypackage, KeypackageRef},
68        session::{
69            EpochObserver, HistoryObserver, Session,
70            id::{ClientId, ClientIdRef},
71            user_id::UserId,
72        },
73    },
74    mls_provider::{CryptoProvider, EntropySeed, RawEntropySeed, RustCrypto},
75    transaction_context::{
76        e2e_identity::conversation_state::E2eiConversationState, key_package::KEYPACKAGE_DEFAULT_LIFETIME,
77    },
78};
79
80/// An entity / data which has been packaged by the application to be encrypted
81/// and transmitted in an application message.
82#[derive(Debug, derive_more::From, derive_more::Deref, serde::Serialize, serde::Deserialize)]
83pub struct TransportData(pub Vec<u8>);
84
85/// Client callbacks to allow communication with the delivery service.
86/// There are two different endpoints, one for messages and one for commit bundles.
87#[cfg_attr(target_os = "unknown", async_trait::async_trait(?Send))]
88#[cfg_attr(not(target_os = "unknown"), async_trait::async_trait)]
89pub trait MlsTransport: std::fmt::Debug + Send + Sync {
90    /// Send a commit bundle to the corresponding endpoint.
91    async fn send_commit_bundle(&self, commit_bundle: CommitBundle) -> Result<()>;
92
93    /// This function will be called before a history secret is sent to the mls transport to allow
94    /// the application to package it in a suitable transport container (json, protobuf, ...).
95    ///
96    /// The `secret` parameter contain the history client's secrets which will be sent over the mls transport.
97    ///
98    /// Returns the history secret packaged for transport
99    async fn prepare_for_transport(&self, secret: &HistorySecret) -> Result<TransportData>;
100}
101
102/// This provider is mainly used for the initialization of the history client session, the only case where transport
103/// doesn't need to be implemented.
104#[derive(Debug, Default)]
105pub struct CoreCryptoTransportNotImplementedProvider();
106
107#[cfg_attr(target_os = "unknown", async_trait::async_trait(?Send))]
108#[cfg_attr(not(target_os = "unknown"), async_trait::async_trait)]
109impl MlsTransport for CoreCryptoTransportNotImplementedProvider {
110    async fn send_commit_bundle(&self, _commit_bundle: CommitBundle) -> crate::Result<()> {
111        Err(Error::MlsTransportNotProvided)
112    }
113
114    async fn prepare_for_transport(&self, _secret: &HistorySecret) -> crate::Result<TransportData> {
115        Err(Error::MlsTransportNotProvided)
116    }
117}
118
119/// Wrapper superstruct for both [mls::session::Session] and [proteus::ProteusCentral]
120///
121/// As [std::ops::Deref] is implemented, this struct is automatically dereferred to [mls::session::Session] apart from
122/// `proteus_*` calls
123#[derive(Debug)]
124pub struct CoreCrypto {
125    database: Arc<Database>,
126    pki_environment: RwLock<Option<Arc<PkiEnvironment>>>,
127    mls: RwLock<Option<mls::session::Session>>,
128    #[cfg(feature = "proteus")]
129    proteus: Mutex<Option<proteus::ProteusCentral>>,
130    #[cfg(not(feature = "proteus"))]
131    #[expect(dead_code)]
132    proteus: (),
133}
134
135impl CoreCrypto {
136    /// Create an new CoreCrypto client without any initialized session.
137    pub fn new(database: Arc<Database>) -> Arc<Self> {
138        Self {
139            database,
140            pki_environment: Default::default(),
141            mls: Default::default(),
142            proteus: Default::default(),
143        }
144        .into()
145    }
146
147    /// Set the session's PKI Environment
148    pub async fn set_pki_environment(&self, pki_environment: Option<Arc<PkiEnvironment>>) {
149        *self.pki_environment.write().await = pki_environment.clone();
150        if let Some(mls_session) = self.mls.write().await.as_mut() {
151            mls_session.crypto_provider.set_pki_environment(pki_environment).await;
152        }
153    }
154
155    /// Get the session's PKI Environment
156    pub async fn get_pki_environment(&self) -> Option<Arc<PkiEnvironment>> {
157        self.pki_environment.read().await.clone()
158    }
159
160    /// Get the mls session if initialized
161    pub async fn mls_session(&self) -> Result<Session> {
162        if let Some(session) = self.mls.read().await.as_ref() {
163            return Ok(session.clone());
164        }
165        let err = Err(mls::session::Error::MlsNotInitialized);
166        err.map_err(RecursiveError::context("Getting mls session"))?
167    }
168
169    /// Get the database
170    pub fn database(&self) -> ImmutableDatabase {
171        self.database.clone().into()
172    }
173}