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}