Skip to main content

offline_protocol/
mls.rs

1//! MLS (Message Layer Security) integration for end-to-end encryption.
2//!
3//! This module provides end-to-end encryption support using the MLS protocol.
4//! Messages can be encrypted before sending and decrypted after receiving,
5//! providing forward secrecy and post-compromise security.
6//!
7//! # Architecture
8//!
9//! The MLS integration uses a storage-agnostic design:
10//! - Apps provide a storage implementation via the [`MlsStorage`] trait
11//! - The SDK handles all MLS protocol operations
12//! - Encrypted messages are transported using the existing transport layer
13//!
14//! # Usage
15//!
16//! ```ignore
17//! use offline_protocol::mls::{MlsManager, MlsStorage};
18//!
19//! // Create storage (apps implement this for platform-native secure storage)
20//! let storage = MySecureStorage::new();
21//!
22//! // Create the MLS manager
23//! let mls = MlsManager::new("user123", storage)?;
24//!
25//! // Generate a key package to share with others
26//! let key_package = mls.generate_key_package()?;
27//!
28//! // Encrypt a message for another user
29//! let encrypted = mls.encrypt_for_user("bob", b"Hello!")?;
30//! ```
31
32// Re-export MLS types from the MLS crate
33pub use offline_protocol_mls::{
34    EncryptedMessage, GroupId, GroupInfo, GroupMetadata, KeyPackageBundle, MlsError, MlsManager,
35    MlsMessageType, MlsStorage, Result as MlsResult, StorageError, WelcomeMessage,
36};
37
38// Re-export the in-memory storage for testing
39pub use offline_protocol_mls::storage::InMemoryStorage;
40
41use crate::{Error, Result};
42use std::sync::Arc;
43
44/// Metadata keys for MLS-encrypted messages.
45pub mod metadata_keys {
46    /// Key indicating the message is encrypted.
47    pub const ENCRYPTED: &str = "mls_encrypted";
48
49    /// Key for the MLS group ID.
50    pub const GROUP_ID: &str = "mls_group_id";
51
52    /// Key for the MLS epoch.
53    pub const EPOCH: &str = "mls_epoch";
54
55    /// Key for the message type (application, welcome, commit).
56    pub const MESSAGE_TYPE: &str = "mls_message_type";
57}
58
59/// Extension trait for integrating MLS with the protocol.
60pub trait MlsProtocolExt {
61    /// Encrypts a plaintext message for a recipient.
62    ///
63    /// This wraps the plaintext in an MLS ciphertext suitable for transport.
64    fn encrypt_message(
65        &self,
66        mls_manager: &MlsManager,
67        recipient: &str,
68        plaintext: &[u8],
69    ) -> Result<String>;
70
71    /// Decrypts an encrypted message.
72    ///
73    /// Returns the decrypted plaintext if successful.
74    fn decrypt_message(
75        &self,
76        mls_manager: &MlsManager,
77        encrypted_content: &str,
78    ) -> Result<Option<Vec<u8>>>;
79}
80
81/// Converts an MLS encrypted message to a base64-encoded string for transport.
82pub fn encode_for_transport(encrypted: &EncryptedMessage) -> Result<String> {
83    encrypted
84        .to_base64()
85        .map_err(|e| Error::Other(format!("Failed to encode encrypted message: {}", e)))
86}
87
88/// Decodes an encrypted message from a base64-encoded string.
89pub fn decode_from_transport(encoded: &str) -> Result<EncryptedMessage> {
90    EncryptedMessage::from_base64(encoded)
91        .map_err(|e| Error::Other(format!("Failed to decode encrypted message: {}", e)))
92}
93
94/// Creates an MLS manager with the given storage.
95///
96/// This is a convenience function for creating an MLS manager.
97pub fn create_manager(
98    user_id: impl Into<String>,
99    storage: Arc<dyn MlsStorage>,
100) -> Result<MlsManager> {
101    MlsManager::new(user_id, storage)
102        .map_err(|e| Error::Other(format!("Failed to create MLS manager: {}", e)))
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_create_manager() {
111        let storage = Arc::new(InMemoryStorage::new());
112        let manager = create_manager("test_user", storage).unwrap();
113        assert_eq!(manager.user_id(), "test_user");
114    }
115
116    #[test]
117    fn test_encode_decode_roundtrip() {
118        let encrypted = EncryptedMessage {
119            group_id: GroupId::new("test-group").unwrap(),
120            message_type: MlsMessageType::Application,
121            epoch: 1,
122            ciphertext: vec![1, 2, 3, 4],
123            sender_id: "alice".to_string(),
124            timestamp_ms: 1234567890,
125        };
126
127        let encoded = encode_for_transport(&encrypted).unwrap();
128        let decoded = decode_from_transport(&encoded).unwrap();
129
130        assert_eq!(encrypted.group_id, decoded.group_id);
131        assert_eq!(encrypted.epoch, decoded.epoch);
132        assert_eq!(encrypted.ciphertext, decoded.ciphertext);
133    }
134}