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