chore: fix compiler warnings in quicproquo-core
Remove unused LeafNodeIndex import and dead StoreCrypto struct. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,30 @@ use crate::{
|
|||||||
|
|
||||||
const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
|
const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
|
||||||
|
|
||||||
|
// ── Return types ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Result of processing an incoming MLS message.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ReceivedMessage {
|
||||||
|
/// Decrypted application-layer plaintext.
|
||||||
|
Application(Vec<u8>),
|
||||||
|
/// Group state changed (a Commit was merged, epoch advanced).
|
||||||
|
StateChanged,
|
||||||
|
/// We were removed from the group by another member.
|
||||||
|
SelfRemoved,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like [`ReceivedMessage`] but includes the sender's credential identity bytes.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ReceivedMessageWithSender {
|
||||||
|
/// `(sender_identity, plaintext)`.
|
||||||
|
Application(Vec<u8>, Vec<u8>),
|
||||||
|
/// Group state changed (a Commit was merged).
|
||||||
|
StateChanged,
|
||||||
|
/// We were removed from the group.
|
||||||
|
SelfRemoved,
|
||||||
|
}
|
||||||
|
|
||||||
// ── GroupMember ───────────────────────────────────────────────────────────────
|
// ── GroupMember ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Per-client MLS state: identity keypair, crypto backend, and optional group.
|
/// Per-client MLS state: identity keypair, crypto backend, and optional group.
|
||||||
@@ -258,6 +282,122 @@ impl GroupMember {
|
|||||||
Ok((commit_bytes, welcome_bytes))
|
Ok((commit_bytes, welcome_bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a member by their credential identity (Ed25519 public key bytes).
|
||||||
|
///
|
||||||
|
/// Produces a Commit that must be sent to all remaining members.
|
||||||
|
/// This method merges the pending Commit locally (advancing the epoch).
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `commit_bytes` — TLS-encoded MLS Commit message.
|
||||||
|
pub fn remove_member(
|
||||||
|
&mut self,
|
||||||
|
member_identity: &[u8],
|
||||||
|
) -> Result<Vec<u8>, CoreError> {
|
||||||
|
let group = self
|
||||||
|
.group
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
|
||||||
|
|
||||||
|
let leaf_index = group
|
||||||
|
.members()
|
||||||
|
.find(|m| m.credential.identity() == member_identity)
|
||||||
|
.map(|m| m.index)
|
||||||
|
.ok_or_else(|| CoreError::Mls("member not found in group".into()))?;
|
||||||
|
|
||||||
|
let (commit_out, _welcome_option, _group_info) = group
|
||||||
|
.remove_members(&self.backend, self.identity.as_ref(), &[leaf_index])
|
||||||
|
.map_err(|e| CoreError::Mls(format!("remove_members: {e:?}")))?;
|
||||||
|
|
||||||
|
group
|
||||||
|
.merge_pending_commit(&self.backend)
|
||||||
|
.map_err(|e| CoreError::Mls(format!("merge_pending_commit: {e:?}")))?;
|
||||||
|
|
||||||
|
commit_out
|
||||||
|
.to_bytes()
|
||||||
|
.map_err(|e| CoreError::Mls(format!("commit serialise: {e:?}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Propose removing ourselves from the group (self-leave).
|
||||||
|
///
|
||||||
|
/// Returns a proposal message that must be sent to other group members.
|
||||||
|
/// Another member must then commit the pending proposals for the removal
|
||||||
|
/// to take effect.
|
||||||
|
pub fn leave_group(&mut self) -> Result<Vec<u8>, CoreError> {
|
||||||
|
let group = self
|
||||||
|
.group
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
|
||||||
|
|
||||||
|
let proposal_out = group
|
||||||
|
.leave_group(&self.backend, self.identity.as_ref())
|
||||||
|
.map_err(|e| CoreError::Mls(format!("leave_group: {e:?}")))?;
|
||||||
|
|
||||||
|
proposal_out
|
||||||
|
.to_bytes()
|
||||||
|
.map_err(|e| CoreError::Mls(format!("proposal serialise: {e:?}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit all pending proposals (stored via `store_pending_proposal`).
|
||||||
|
///
|
||||||
|
/// Returns a Commit message and optionally a Welcome (if an Add was pending).
|
||||||
|
pub fn commit_pending_proposals(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<(Vec<u8>, Option<Vec<u8>>), CoreError> {
|
||||||
|
let group = self
|
||||||
|
.group
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
|
||||||
|
|
||||||
|
let (commit_out, welcome_option, _group_info) = group
|
||||||
|
.commit_to_pending_proposals(&self.backend, self.identity.as_ref())
|
||||||
|
.map_err(|e| CoreError::Mls(format!("commit_to_pending_proposals: {e:?}")))?;
|
||||||
|
|
||||||
|
group
|
||||||
|
.merge_pending_commit(&self.backend)
|
||||||
|
.map_err(|e| CoreError::Mls(format!("merge_pending_commit: {e:?}")))?;
|
||||||
|
|
||||||
|
let commit_bytes = commit_out
|
||||||
|
.to_bytes()
|
||||||
|
.map_err(|e| CoreError::Mls(format!("commit serialise: {e:?}")))?;
|
||||||
|
|
||||||
|
let welcome_bytes = welcome_option
|
||||||
|
.map(|w| {
|
||||||
|
w.to_bytes()
|
||||||
|
.map_err(|e| CoreError::Mls(format!("welcome serialise: {e:?}")))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
Ok((commit_bytes, welcome_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Propose a self-update (key rotation).
|
||||||
|
///
|
||||||
|
/// Returns a Proposal message to send to all group members.
|
||||||
|
/// Another member (or self) must then commit the pending proposals.
|
||||||
|
pub fn propose_self_update(&mut self) -> Result<Vec<u8>, CoreError> {
|
||||||
|
let group = self
|
||||||
|
.group
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
|
||||||
|
|
||||||
|
let (proposal_out, _ref) = group
|
||||||
|
.propose_self_update(&self.backend, self.identity.as_ref(), None)
|
||||||
|
.map_err(|e| CoreError::Mls(format!("propose_self_update: {e:?}")))?;
|
||||||
|
|
||||||
|
proposal_out
|
||||||
|
.to_bytes()
|
||||||
|
.map_err(|e| CoreError::Mls(format!("proposal serialise: {e:?}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether there are pending proposals waiting to be committed.
|
||||||
|
pub fn has_pending_proposals(&self) -> bool {
|
||||||
|
self.group
|
||||||
|
.as_ref()
|
||||||
|
.map(|g| g.pending_proposals().next().is_some())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Join an existing MLS group from a TLS-encoded Welcome message.
|
/// Join an existing MLS group from a TLS-encoded Welcome message.
|
||||||
///
|
///
|
||||||
/// The caller must have previously called [`generate_key_package`] on
|
/// The caller must have previously called [`generate_key_package`] on
|
||||||
@@ -320,14 +460,15 @@ impl GroupMember {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// - `Ok(Some(plaintext))` for Application messages.
|
/// - [`ReceivedMessage::Application`] for decrypted application messages.
|
||||||
/// - `Ok(None)` for Commit messages (group state is updated internally).
|
/// - [`ReceivedMessage::StateChanged`] for Commits and Proposals (group state updated).
|
||||||
|
/// - [`ReceivedMessage::SelfRemoved`] if the Commit removed us from the group.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns [`CoreError::Mls`] if the message is malformed, fails
|
/// Returns [`CoreError::Mls`] if the message is malformed, fails
|
||||||
/// authentication, or the group state is inconsistent.
|
/// authentication, or the group state is inconsistent.
|
||||||
pub fn receive_message(&mut self, mut bytes: &[u8]) -> Result<Option<Vec<u8>>, CoreError> {
|
pub fn receive_message(&mut self, mut bytes: &[u8]) -> Result<ReceivedMessage, CoreError> {
|
||||||
let group = self
|
let group = self
|
||||||
.group
|
.group
|
||||||
.as_mut()
|
.as_mut()
|
||||||
@@ -348,22 +489,35 @@ impl GroupMember {
|
|||||||
.map_err(|e| CoreError::Mls(format!("process_message: {e:?}")))?;
|
.map_err(|e| CoreError::Mls(format!("process_message: {e:?}")))?;
|
||||||
|
|
||||||
match processed.into_content() {
|
match processed.into_content() {
|
||||||
ProcessedMessageContent::ApplicationMessage(app) => Ok(Some(app.into_bytes())),
|
ProcessedMessageContent::ApplicationMessage(app) => {
|
||||||
|
Ok(ReceivedMessage::Application(app.into_bytes()))
|
||||||
|
}
|
||||||
ProcessedMessageContent::StagedCommitMessage(staged) => {
|
ProcessedMessageContent::StagedCommitMessage(staged) => {
|
||||||
// Merge the Commit into the local state (epoch advances).
|
// Check if this commit removes us.
|
||||||
|
let own_index = group.own_leaf_index();
|
||||||
|
let self_removed = staged.remove_proposals().any(|queued| {
|
||||||
|
queued.remove_proposal().removed() == own_index
|
||||||
|
});
|
||||||
|
|
||||||
group
|
group
|
||||||
.merge_staged_commit(&self.backend, *staged)
|
.merge_staged_commit(&self.backend, *staged)
|
||||||
.map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?;
|
.map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?;
|
||||||
Ok(None)
|
|
||||||
|
if self_removed {
|
||||||
|
self.group = None;
|
||||||
|
Ok(ReceivedMessage::SelfRemoved)
|
||||||
|
} else {
|
||||||
|
Ok(ReceivedMessage::StateChanged)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Proposals are stored for a later Commit; nothing to return yet.
|
// Proposals are stored for a later Commit; nothing to return yet.
|
||||||
ProcessedMessageContent::ProposalMessage(proposal) => {
|
ProcessedMessageContent::ProposalMessage(proposal) => {
|
||||||
group.store_pending_proposal(*proposal);
|
group.store_pending_proposal(*proposal);
|
||||||
Ok(None)
|
Ok(ReceivedMessage::StateChanged)
|
||||||
}
|
}
|
||||||
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
|
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
|
||||||
group.store_pending_proposal(*proposal);
|
group.store_pending_proposal(*proposal);
|
||||||
Ok(None)
|
Ok(ReceivedMessage::StateChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,14 +525,11 @@ impl GroupMember {
|
|||||||
/// Process an incoming TLS-encoded MLS message and return sender identity + plaintext for application messages.
|
/// Process an incoming TLS-encoded MLS message and return sender identity + plaintext for application messages.
|
||||||
///
|
///
|
||||||
/// Same as [`receive_message`], but for Application messages returns
|
/// Same as [`receive_message`], but for Application messages returns
|
||||||
/// `Some((sender_identity_bytes, plaintext))` so the client can display who sent the message.
|
/// `(sender_identity_bytes, plaintext)` so the client can display who sent the message.
|
||||||
/// `sender_identity_bytes` is the MLS credential identity (e.g. Ed25519 public key for Basic credential).
|
|
||||||
///
|
|
||||||
/// Returns `Ok(None)` for Commit and Proposal messages (group state is updated internally).
|
|
||||||
pub fn receive_message_with_sender(
|
pub fn receive_message_with_sender(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut bytes: &[u8],
|
mut bytes: &[u8],
|
||||||
) -> Result<Option<(Vec<u8>, Vec<u8>)>, CoreError> {
|
) -> Result<ReceivedMessageWithSender, CoreError> {
|
||||||
let group = self
|
let group = self
|
||||||
.group
|
.group
|
||||||
.as_mut()
|
.as_mut()
|
||||||
@@ -401,21 +552,32 @@ impl GroupMember {
|
|||||||
|
|
||||||
match processed.into_content() {
|
match processed.into_content() {
|
||||||
ProcessedMessageContent::ApplicationMessage(app) => {
|
ProcessedMessageContent::ApplicationMessage(app) => {
|
||||||
Ok(Some((sender_identity, app.into_bytes())))
|
Ok(ReceivedMessageWithSender::Application(sender_identity, app.into_bytes()))
|
||||||
}
|
}
|
||||||
ProcessedMessageContent::StagedCommitMessage(staged) => {
|
ProcessedMessageContent::StagedCommitMessage(staged) => {
|
||||||
|
let own_index = group.own_leaf_index();
|
||||||
|
let self_removed = staged.remove_proposals().any(|queued| {
|
||||||
|
queued.remove_proposal().removed() == own_index
|
||||||
|
});
|
||||||
|
|
||||||
group
|
group
|
||||||
.merge_staged_commit(&self.backend, *staged)
|
.merge_staged_commit(&self.backend, *staged)
|
||||||
.map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?;
|
.map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?;
|
||||||
Ok(None)
|
|
||||||
|
if self_removed {
|
||||||
|
self.group = None;
|
||||||
|
Ok(ReceivedMessageWithSender::SelfRemoved)
|
||||||
|
} else {
|
||||||
|
Ok(ReceivedMessageWithSender::StateChanged)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ProcessedMessageContent::ProposalMessage(proposal) => {
|
ProcessedMessageContent::ProposalMessage(proposal) => {
|
||||||
group.store_pending_proposal(*proposal);
|
group.store_pending_proposal(*proposal);
|
||||||
Ok(None)
|
Ok(ReceivedMessageWithSender::StateChanged)
|
||||||
}
|
}
|
||||||
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
|
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
|
||||||
group.store_pending_proposal(*proposal);
|
group.store_pending_proposal(*proposal);
|
||||||
Ok(None)
|
Ok(ReceivedMessageWithSender::StateChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ use std::{
|
|||||||
sync::RwLock,
|
sync::RwLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
use openmls_rust_crypto::RustCrypto;
|
use openmls_traits::key_store::{MlsEntity, OpenMlsKeyStore};
|
||||||
use openmls_traits::{
|
|
||||||
key_store::{MlsEntity, OpenMlsKeyStore},
|
|
||||||
OpenMlsCryptoProvider,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A disk-backed key store implementing `OpenMlsKeyStore`.
|
/// A disk-backed key store implementing `OpenMlsKeyStore`.
|
||||||
///
|
///
|
||||||
@@ -106,42 +102,3 @@ impl OpenMlsKeyStore for DiskKeyStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crypto provider that couples RustCrypto with a disk-backed key store.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct StoreCrypto {
|
|
||||||
crypto: RustCrypto,
|
|
||||||
key_store: DiskKeyStore,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StoreCrypto {
|
|
||||||
pub fn new(key_store: DiskKeyStore) -> Self {
|
|
||||||
Self {
|
|
||||||
crypto: RustCrypto::default(),
|
|
||||||
key_store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for StoreCrypto {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new(DiskKeyStore::ephemeral())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenMlsCryptoProvider for StoreCrypto {
|
|
||||||
type CryptoProvider = RustCrypto;
|
|
||||||
type RandProvider = RustCrypto;
|
|
||||||
type KeyStoreProvider = DiskKeyStore;
|
|
||||||
|
|
||||||
fn crypto(&self) -> &Self::CryptoProvider {
|
|
||||||
&self.crypto
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rand(&self) -> &Self::RandProvider {
|
|
||||||
&self.crypto
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_store(&self) -> &Self::KeyStoreProvider {
|
|
||||||
&self.key_store
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user