use openmls_traits::{crypto::OpenMlsCrypto, types::Ciphersuite};
use proposal_store::QueuedProposal;
use crate::{
binary_tree::LeafNodeIndex,
ciphersuite::signable::Verifiable,
error::LibraryError,
extensions::ExternalSendersExtension,
group::{errors::ValidationError, mls_group::staged_commit::StagedCommit},
tree::sender_ratchet::SenderRatchetConfiguration,
versions::ProtocolVersion,
};
#[cfg(feature = "extensions-draft")]
use crate::{
component::ComponentId,
framing::safe_aad::SafeAad,
group::{
errors::StageCommitError,
mls_group::{errors::ResolveAppDataCommitError, processing::UnresolvedAppDataCommit},
},
};
use super::{
mls_auth_content::AuthenticatedContent,
mls_auth_content_in::{AuthenticatedContentIn, VerifiableAuthenticatedContentIn},
private_message_in::PrivateMessageIn,
public_message_in::PublicMessageIn,
*,
};
#[derive(Debug)]
pub(crate) enum InboundDecryptionResult {
Decrypted(DecryptedMessage),
OwnPrivateMessage {
epoch: GroupEpoch,
authenticated_data: Vec<u8>,
},
}
impl InboundDecryptionResult {
#[cfg(test)]
pub(crate) fn into_decrypted(self) -> Option<DecryptedMessage> {
match self {
Self::Decrypted(message) => Some(message),
Self::OwnPrivateMessage { .. } => None,
}
}
}
#[derive(Debug)]
pub(crate) struct DecryptedMessage {
verifiable_content: VerifiableAuthenticatedContentIn,
#[cfg(feature = "virtual-clients-draft")]
emulator_sender_leaf_index: Option<LeafNodeIndex>,
}
impl DecryptedMessage {
pub(crate) fn from_inbound_public_message<'a>(
public_message: PublicMessageIn,
message_secrets_option: impl Into<Option<&'a MessageSecrets>>,
serialized_context: Vec<u8>,
crypto: &impl OpenMlsCrypto,
ciphersuite: Ciphersuite,
) -> Result<Self, ValidationError> {
if public_message.sender().is_member() {
if public_message.membership_tag().is_none() {
return Err(ValidationError::MissingMembershipTag);
}
if let Some(message_secrets) = message_secrets_option.into() {
public_message.verify_membership(
crypto,
ciphersuite,
message_secrets.membership_key(),
message_secrets.serialized_context(),
)?;
}
}
let verifiable_content = public_message.into_verifiable_content(serialized_context);
Self::from_verifiable_content(
verifiable_content,
#[cfg(feature = "virtual-clients-draft")]
None,
)
}
pub(crate) fn from_inbound_ciphertext(
ciphertext: PrivateMessageIn,
crypto: &impl OpenMlsCrypto,
group: &mut MlsGroup,
sender_ratchet_configuration: &SenderRatchetConfiguration,
#[cfg(feature = "virtual-clients-draft")] emulator_ctx: Option<
&crate::framing::private_message::EmulatorReuseGuardCtx<'_>,
>,
) -> Result<InboundDecryptionResult, ValidationError> {
let ciphersuite = group.ciphersuite();
let (message_secrets, _old_leaves) = group
.message_secrets_and_leaves(ciphertext.epoch())
.map_err(MessageDecryptionError::SecretTreeError)?;
let sender_data = ciphertext.sender_data(message_secrets, crypto, ciphersuite)?;
let own_sender = sender_data.leaf_index == group.own_leaf_index();
#[cfg(not(feature = "virtual-clients-draft"))]
if own_sender {
return Ok(InboundDecryptionResult::OwnPrivateMessage {
epoch: ciphertext.epoch(),
authenticated_data: ciphertext.aad().to_vec(),
});
}
#[cfg(feature = "virtual-clients-draft")]
let effective_emulator_ctx = match emulator_ctx {
Some(ctx) if own_sender => Some(ctx),
_ => None,
};
let message_secrets = group
.message_secrets_for_epoch_mut(ciphertext.epoch())
.map_err(|_| MessageDecryptionError::AeadError)?;
let decrypt_result = ciphertext.to_verifiable_content(
ciphersuite,
crypto,
message_secrets,
sender_data.leaf_index,
sender_ratchet_configuration,
sender_data,
#[cfg(feature = "virtual-clients-draft")]
effective_emulator_ctx,
);
#[cfg(not(feature = "virtual-clients-draft"))]
let decrypted = decrypt_result?;
#[cfg(feature = "virtual-clients-draft")]
let decrypted = match decrypt_result {
Ok(decrypted) => decrypted,
Err(_) if own_sender && emulator_ctx.is_none() => {
return Ok(InboundDecryptionResult::OwnPrivateMessage {
epoch: ciphertext.epoch(),
authenticated_data: ciphertext.aad().to_vec(),
});
}
Err(e) => return Err(e.into()),
};
Self::from_verifiable_content(
decrypted.verifiable,
#[cfg(feature = "virtual-clients-draft")]
decrypted.emulator_sender_leaf_index,
)
.map(InboundDecryptionResult::Decrypted)
}
fn from_verifiable_content(
verifiable_content: VerifiableAuthenticatedContentIn,
#[cfg(feature = "virtual-clients-draft")] emulator_sender_leaf_index: Option<LeafNodeIndex>,
) -> Result<Self, ValidationError> {
if verifiable_content.content_type() == ContentType::Commit
&& verifiable_content.confirmation_tag().is_none()
{
return Err(ValidationError::MissingConfirmationTag);
}
if verifiable_content.content_type() == ContentType::Application {
if verifiable_content.wire_format() != WireFormat::PrivateMessage {
return Err(ValidationError::UnencryptedApplicationMessage);
} else if !verifiable_content.sender().is_member() {
return Err(LibraryError::custom("Expected sender to be member.").into());
}
}
Ok(DecryptedMessage {
verifiable_content,
#[cfg(feature = "virtual-clients-draft")]
emulator_sender_leaf_index,
})
}
#[cfg(feature = "virtual-clients-draft")]
#[allow(dead_code)]
pub(crate) fn emulator_sender_leaf_index(&self) -> Option<LeafNodeIndex> {
self.emulator_sender_leaf_index
}
pub(crate) fn credential(
&self,
look_up_credential_with_key: impl Fn(LeafNodeIndex) -> Option<CredentialWithKey>,
external_senders: Option<&ExternalSendersExtension>,
) -> Result<CredentialWithKey, ValidationError> {
let sender = self.sender();
match sender {
Sender::Member(leaf_index) => {
look_up_credential_with_key(*leaf_index).ok_or(ValidationError::UnknownMember)
}
Sender::External(index) => {
let sender = external_senders
.ok_or(ValidationError::NoExternalSendersExtension)?
.get(index.index())
.ok_or(ValidationError::UnauthorizedExternalSender)?;
Ok(CredentialWithKey {
credential: sender.credential().clone(),
signature_key: sender.signature_key().clone(),
})
}
Sender::NewMemberCommit | Sender::NewMemberProposal => {
self.verifiable_content.new_member_credential()
}
}
}
pub fn sender(&self) -> &Sender {
self.verifiable_content.sender()
}
pub(crate) fn verifiable_content(&self) -> &VerifiableAuthenticatedContentIn {
&self.verifiable_content
}
}
pub(crate) struct VerifiedMessage {
pub(crate) content: AuthenticatedContent,
pub(crate) credential: Credential,
#[cfg(feature = "virtual-clients-draft")]
pub(crate) emulator_sender_leaf_index: Option<LeafNodeIndex>,
}
#[derive(Debug, Clone)]
pub(crate) enum SenderContext {
Member((GroupId, LeafNodeIndex)),
ExternalCommit {
group_id: GroupId,
leftmost_blank_index: LeafNodeIndex,
self_removes_in_store: Vec<SelfRemoveInStore>,
},
}
#[derive(Debug, Clone)]
pub struct UnverifiedMessage {
verifiable_content: VerifiableAuthenticatedContentIn,
credential: Credential,
sender_pk: OpenMlsSignaturePublicKey,
sender_context: Option<SenderContext>,
#[cfg(feature = "virtual-clients-draft")]
emulator_sender_leaf_index: Option<LeafNodeIndex>,
}
impl UnverifiedMessage {
pub(crate) fn from_decrypted_message(
decrypted_message: DecryptedMessage,
credential: Credential,
sender_pk: OpenMlsSignaturePublicKey,
sender_context: Option<SenderContext>,
) -> Self {
#[cfg(feature = "virtual-clients-draft")]
let emulator_sender_leaf_index = decrypted_message.emulator_sender_leaf_index;
UnverifiedMessage {
verifiable_content: decrypted_message.verifiable_content,
credential,
sender_pk,
sender_context,
#[cfg(feature = "virtual-clients-draft")]
emulator_sender_leaf_index,
}
}
pub(crate) fn verify(
self,
ciphersuite: Ciphersuite,
crypto: &impl OpenMlsCrypto,
protocol_version: ProtocolVersion,
) -> Result<VerifiedMessage, ValidationError> {
let content: AuthenticatedContentIn = self
.verifiable_content
.verify(crypto, &self.sender_pk)
.map_err(|_| ValidationError::InvalidSignature)?;
let content =
content.validate(ciphersuite, crypto, self.sender_context, protocol_version)?;
Ok(VerifiedMessage {
content,
credential: self.credential,
#[cfg(feature = "virtual-clients-draft")]
emulator_sender_leaf_index: self.emulator_sender_leaf_index,
})
}
}
#[derive(Debug)]
pub struct ProcessedMessage {
group_id: GroupId,
epoch: GroupEpoch,
sender: Sender,
authenticated_data: Vec<u8>,
content: ProcessedMessageContent,
credential: Credential,
#[cfg(feature = "virtual-clients-draft")]
emulator_sender_leaf_index: Option<LeafNodeIndex>,
#[cfg(feature = "extensions-draft")]
safe_aad: Option<SafeAad>,
#[cfg(feature = "extensions-draft")]
safe_aad_prefix_len: usize,
}
impl ProcessedMessage {
pub(crate) fn new(
group_id: GroupId,
epoch: GroupEpoch,
sender: Sender,
authenticated_data: Vec<u8>,
content: ProcessedMessageContent,
credential: Credential,
#[cfg(feature = "virtual-clients-draft")] emulator_sender_leaf_index: Option<LeafNodeIndex>,
) -> Self {
Self {
group_id,
epoch,
sender,
authenticated_data,
content,
credential,
#[cfg(feature = "virtual-clients-draft")]
emulator_sender_leaf_index,
#[cfg(feature = "extensions-draft")]
safe_aad: None,
#[cfg(feature = "extensions-draft")]
safe_aad_prefix_len: 0,
}
}
#[cfg(feature = "extensions-draft")]
pub(crate) fn resolve_app_data_commit(
mut self,
stage: impl FnOnce(UnresolvedAppDataCommit) -> Result<StagedCommit, StageCommitError>,
) -> Result<Self, ResolveAppDataCommitError> {
let ProcessedMessageContent::UnresolvedAppDataCommit(unresolved_commit) = self.content
else {
return Err(ResolveAppDataCommitError::NotAnUnresolvedAppDataCommit);
};
let staged_commit = stage(*unresolved_commit)?;
self.content = ProcessedMessageContent::StagedCommitMessage(Box::new(staged_commit));
Ok(self)
}
#[cfg(feature = "extensions-draft")]
pub(crate) fn try_attach_safe_aad(&mut self) -> Result<(), crate::framing::SafeAadError> {
let (safe_aad, prefix_len) =
crate::framing::safe_aad::parse_authenticated_data_prefix(&self.authenticated_data)?;
self.safe_aad = Some(safe_aad);
self.safe_aad_prefix_len = prefix_len;
Ok(())
}
#[cfg(feature = "extensions-draft")]
pub fn safe_aad(&self) -> Option<&SafeAad> {
self.safe_aad.as_ref()
}
#[cfg(feature = "extensions-draft")]
pub fn safe_aad_item(&self, component_id: crate::component::ComponentId) -> Option<&[u8]> {
self.safe_aad
.as_ref()
.and_then(|safe_aad| safe_aad.get(component_id))
}
#[cfg(feature = "extensions-draft")]
pub fn tail_aad(&self) -> &[u8] {
&self.authenticated_data[self.safe_aad_prefix_len..]
}
#[cfg(feature = "virtual-clients-draft")]
pub fn emulator_sender_leaf_index(&self) -> Option<LeafNodeIndex> {
self.emulator_sender_leaf_index
}
pub fn group_id(&self) -> &GroupId {
&self.group_id
}
pub fn epoch(&self) -> GroupEpoch {
self.epoch
}
pub fn sender(&self) -> &Sender {
&self.sender
}
pub fn aad(&self) -> &[u8] {
&self.authenticated_data
}
pub fn content(&self) -> &ProcessedMessageContent {
&self.content
}
pub fn into_content(self) -> ProcessedMessageContent {
self.content
}
pub fn credential(&self) -> &Credential {
&self.credential
}
#[cfg(feature = "extensions-draft")]
pub fn safe_export_secret<Crypto: OpenMlsCrypto>(
&mut self,
crypto: &Crypto,
component_id: ComponentId,
) -> Result<Vec<u8>, ProcessedMessageSafeExportSecretError> {
if let ProcessedMessageContent::StagedCommitMessage(ref mut staged_commit) =
&mut self.content
{
let secret = staged_commit.safe_export_secret(crypto, component_id)?;
Ok(secret)
} else {
Err(ProcessedMessageSafeExportSecretError::NotACommit)
}
}
}
#[derive(Debug)]
pub enum ProcessedMessageContent {
ApplicationMessage(ApplicationMessage),
ProposalMessage(Box<QueuedProposal>),
ExternalJoinProposalMessage(Box<QueuedProposal>),
StagedCommitMessage(Box<StagedCommit>),
OwnPendingCommit,
OwnPrivateMessage,
#[cfg(feature = "extensions-draft")]
UnresolvedAppDataCommit(Box<UnresolvedAppDataCommit>),
}
#[derive(Debug, PartialEq, Eq)]
pub struct ApplicationMessage {
bytes: Vec<u8>,
}
impl ApplicationMessage {
pub(crate) fn new(bytes: Vec<u8>) -> Self {
Self { bytes }
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}