use crate::{
ciphersuite::{
hash_ref::KeyPackageRef,
signable::{Signable, SignedStruct, Verifiable, VerifiedStruct},
},
error::LibraryError,
group::errors::ValidationError,
};
use super::*;
use openmls_traits::OpenMlsCryptoProvider;
use std::convert::TryFrom;
use tls_codec::{Serialize, TlsByteVecU32, TlsDeserialize, TlsSerialize, TlsSize};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsSize)]
pub(crate) struct MlsPlaintext {
wire_format: WireFormat,
group_id: GroupId,
epoch: GroupEpoch,
sender: Sender,
authenticated_data: TlsByteVecU32,
content_type: ContentType,
content: MlsPlaintextContentType,
signature: Signature,
confirmation_tag: Option<ConfirmationTag>,
membership_tag: Option<MembershipTag>,
}
pub(crate) struct Payload {
pub(crate) payload: MlsPlaintextContentType,
pub(crate) content_type: ContentType,
}
impl MlsPlaintext {
pub(super) fn signature(&self) -> &Signature {
&self.signature
}
pub(super) fn wire_format(&self) -> WireFormat {
self.wire_format
}
#[cfg(test)]
pub(super) fn unset_confirmation_tag(&mut self) {
self.confirmation_tag = None;
}
#[cfg(test)]
pub(super) fn set_content(&mut self, content: MlsPlaintextContentType) {
self.content = content;
}
#[cfg(test)]
pub(super) fn set_wire_format(&mut self, wire_format: WireFormat) {
self.wire_format = wire_format;
}
}
impl MlsPlaintext {
#[inline]
fn new(
framing_parameters: FramingParameters,
sender: Sender,
payload: Payload,
credential_bundle: &CredentialBundle,
context: &GroupContext,
backend: &impl OpenMlsCryptoProvider,
) -> Result<Self, LibraryError> {
let mut mls_plaintext = MlsPlaintextTbs::new(
framing_parameters.wire_format(),
context.group_id().clone(),
context.epoch(),
sender.clone(),
framing_parameters.aad().into(),
payload,
);
if let Sender::Member(_) = sender {
let serialized_context = context
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?;
mls_plaintext = mls_plaintext.with_context(serialized_context);
}
mls_plaintext.sign(backend, credential_bundle)
}
#[inline]
fn new_with_membership_tag(
framing_parameters: FramingParameters,
sender_reference: &KeyPackageRef,
payload: Payload,
credential_bundle: &CredentialBundle,
context: &GroupContext,
membership_key: &MembershipKey,
backend: &impl OpenMlsCryptoProvider,
) -> Result<Self, LibraryError> {
let sender = Sender::build_member(sender_reference);
let mut mls_plaintext = Self::new(
framing_parameters,
sender,
payload,
credential_bundle,
context,
backend,
)?;
mls_plaintext.set_membership_tag(
backend,
&context
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?,
membership_key,
)?;
Ok(mls_plaintext)
}
pub(crate) fn member_proposal(
framing_parameters: FramingParameters,
sender_reference: &KeyPackageRef,
proposal: Proposal,
credential_bundle: &CredentialBundle,
context: &GroupContext,
membership_key: &MembershipKey,
backend: &impl OpenMlsCryptoProvider,
) -> Result<Self, LibraryError> {
Self::new_with_membership_tag(
framing_parameters,
sender_reference,
Payload {
payload: MlsPlaintextContentType::Proposal(proposal),
content_type: ContentType::Proposal,
},
credential_bundle,
context,
membership_key,
backend,
)
}
pub(crate) fn commit(
framing_parameters: FramingParameters,
sender: Sender,
commit: Commit,
credential_bundle: &CredentialBundle,
context: &GroupContext,
backend: &impl OpenMlsCryptoProvider,
) -> Result<Self, LibraryError> {
Self::new(
framing_parameters,
sender,
Payload {
payload: MlsPlaintextContentType::Commit(commit),
content_type: ContentType::Commit,
},
credential_bundle,
context,
backend,
)
}
pub(crate) fn new_application(
sender_reference: &KeyPackageRef,
authenticated_data: &[u8],
application_message: &[u8],
credential_bundle: &CredentialBundle,
context: &GroupContext,
membership_key: &MembershipKey,
backend: &impl OpenMlsCryptoProvider,
) -> Result<Self, LibraryError> {
let framing_parameters =
FramingParameters::new(authenticated_data, WireFormat::MlsCiphertext);
Self::new_with_membership_tag(
framing_parameters,
sender_reference,
Payload {
payload: MlsPlaintextContentType::Application(application_message.into()),
content_type: ContentType::Application,
},
credential_bundle,
context,
membership_key,
backend,
)
}
pub(crate) fn content(&self) -> &MlsPlaintextContentType {
&self.content
}
pub(crate) fn content_type(&self) -> ContentType {
self.content_type
}
pub(crate) fn sender(&self) -> &Sender {
&self.sender
}
pub(crate) fn set_membership_tag(
&mut self,
backend: &impl OpenMlsCryptoProvider,
serialized_context: &[u8],
membership_key: &MembershipKey,
) -> Result<(), LibraryError> {
let tbs_payload =
encode_tbs(self, serialized_context).map_err(LibraryError::missing_bound_check)?;
let tbm_payload =
MlsPlaintextTbmPayload::new(&tbs_payload, &self.signature, &self.confirmation_tag)?;
let membership_tag = membership_key.tag(backend, tbm_payload)?;
self.membership_tag = Some(membership_tag);
Ok(())
}
#[cfg(any(feature = "test-utils", test))]
pub(crate) fn remove_membership_tag(&mut self) {
self.membership_tag = None;
}
#[cfg(test)]
pub(crate) fn is_handshake_message(&self) -> bool {
self.content_type.is_handshake_message()
}
pub(crate) fn epoch(&self) -> GroupEpoch {
self.epoch
}
pub(crate) fn set_confirmation_tag(&mut self, tag: ConfirmationTag) {
self.confirmation_tag = Some(tag)
}
pub(crate) fn confirmation_tag(&self) -> Option<&ConfirmationTag> {
self.confirmation_tag.as_ref()
}
pub(crate) fn authenticated_data(&self) -> &[u8] {
self.authenticated_data.as_slice()
}
}
#[derive(
PartialEq, Clone, Copy, Debug, Serialize, Deserialize, TlsDeserialize, TlsSerialize, TlsSize,
)]
#[repr(u8)]
pub enum ContentType {
Application = 1,
Proposal = 2,
Commit = 3,
}
impl TryFrom<u8> for ContentType {
type Error = tls_codec::Error;
fn try_from(value: u8) -> Result<Self, tls_codec::Error> {
match value {
1 => Ok(ContentType::Application),
2 => Ok(ContentType::Proposal),
3 => Ok(ContentType::Commit),
_ => Err(tls_codec::Error::DecodingError(format!(
"{} is not a valid content type",
value
))),
}
}
}
impl From<&MlsPlaintextContentType> for ContentType {
fn from(value: &MlsPlaintextContentType) -> Self {
match value {
MlsPlaintextContentType::Application(_) => ContentType::Application,
MlsPlaintextContentType::Proposal(_) => ContentType::Proposal,
MlsPlaintextContentType::Commit(_) => ContentType::Commit,
}
}
}
impl ContentType {
pub(crate) fn is_handshake_message(&self) -> bool {
self == &ContentType::Proposal || self == &ContentType::Commit
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub(crate) enum MlsPlaintextContentType {
Application(TlsByteVecU32),
Proposal(Proposal),
Commit(Commit),
}
impl From<MlsPlaintext> for MlsPlaintextContentType {
fn from(plaintext: MlsPlaintext) -> Self {
plaintext.content
}
}
#[derive(Debug)]
pub(crate) struct MlsPlaintextTbmPayload<'a> {
tbs_payload: &'a [u8],
signature: &'a Signature,
confirmation_tag: &'a Option<ConfirmationTag>,
}
impl<'a> MlsPlaintextTbmPayload<'a> {
pub(crate) fn new(
tbs_payload: &'a [u8],
signature: &'a Signature,
confirmation_tag: &'a Option<ConfirmationTag>,
) -> Result<Self, LibraryError> {
Ok(Self {
tbs_payload,
signature,
confirmation_tag,
})
}
pub(crate) fn into_bytes(self) -> Result<Vec<u8>, tls_codec::Error> {
let mut buffer = self.tbs_payload.to_vec();
self.signature.tls_serialize(&mut buffer)?;
self.confirmation_tag.tls_serialize(&mut buffer)?;
Ok(buffer)
}
}
#[derive(
Debug, PartialEq, Clone, Serialize, Deserialize, TlsSerialize, TlsDeserialize, TlsSize,
)]
pub(crate) struct MembershipTag(pub(crate) Mac);
#[derive(PartialEq, Debug, Clone)]
pub(crate) struct MlsPlaintextTbs {
pub(super) serialized_context: Option<Vec<u8>>,
pub(super) wire_format: WireFormat,
pub(super) group_id: GroupId,
pub(super) epoch: GroupEpoch,
pub(super) sender: Sender,
pub(super) authenticated_data: TlsByteVecU32,
pub(super) content_type: ContentType,
pub(super) payload: MlsPlaintextContentType,
}
fn encode_tbs<'a>(
plaintext: &MlsPlaintext,
serialized_context: impl Into<Option<&'a [u8]>>,
) -> Result<Vec<u8>, tls_codec::Error> {
let mut out = Vec::new();
codec::serialize_plaintext_tbs(
serialized_context,
plaintext.wire_format,
&plaintext.group_id,
&plaintext.epoch,
&plaintext.sender,
&plaintext.authenticated_data,
&plaintext.content_type,
&plaintext.content,
&mut out,
)?;
Ok(out)
}
#[derive(PartialEq, Debug, Clone)]
pub(crate) struct VerifiableMlsPlaintext {
pub(super) tbs: MlsPlaintextTbs,
pub(super) signature: Signature,
pub(super) confirmation_tag: Option<ConfirmationTag>,
pub(super) membership_tag: Option<MembershipTag>,
}
impl VerifiableMlsPlaintext {
pub(crate) fn new(
tbs: MlsPlaintextTbs,
signature: Signature,
confirmation_tag: impl Into<Option<ConfirmationTag>>,
membership_tag: impl Into<Option<MembershipTag>>,
) -> Self {
Self {
tbs,
signature,
confirmation_tag: confirmation_tag.into(),
membership_tag: membership_tag.into(),
}
}
pub(crate) fn from_plaintext(
mls_plaintext: MlsPlaintext,
serialized_context: impl Into<Option<Vec<u8>>>,
) -> Self {
let signature = mls_plaintext.signature.clone();
let membership_tag = mls_plaintext.membership_tag.clone();
let confirmation_tag = mls_plaintext.confirmation_tag.clone();
match serialized_context.into() {
Some(context) => Self {
tbs: MlsPlaintextTbs::from_plaintext(mls_plaintext).with_context(context),
signature,
confirmation_tag,
membership_tag,
},
None => Self {
tbs: MlsPlaintextTbs::from_plaintext(mls_plaintext),
signature,
confirmation_tag,
membership_tag,
},
}
}
pub(crate) fn verify_membership(
&self,
backend: &impl OpenMlsCryptoProvider,
membership_key: &MembershipKey,
) -> Result<(), ValidationError> {
log::debug!("Verifying membership tag.");
log_crypto!(trace, " Membership key: {:x?}", membership_key);
log_crypto!(trace, " Serialized context: {:x?}", serialized_context);
let tbs_payload = self
.tbs
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?;
let tbm_payload =
MlsPlaintextTbmPayload::new(&tbs_payload, &self.signature, &self.confirmation_tag)?;
let expected_membership_tag = &membership_key.tag(backend, tbm_payload)?;
if let Some(membership_tag) = &self.membership_tag {
if membership_tag != expected_membership_tag {
return Err(ValidationError::InvalidMembershipTag);
}
} else {
return Err(ValidationError::MissingMembershipTag);
}
Ok(())
}
pub fn sender(&self) -> &Sender {
&self.tbs.sender
}
#[cfg(test)]
pub(crate) fn set_sender(&mut self, sender: Sender) {
self.tbs.sender = sender;
}
pub(crate) fn group_id(&self) -> &GroupId {
&self.tbs.group_id
}
#[cfg(test)]
pub(crate) fn set_group_id(&mut self, group_id: GroupId) {
self.tbs.group_id = group_id;
}
pub(crate) fn set_context(&mut self, serialized_context: Vec<u8>) {
self.tbs.serialized_context = Some(serialized_context);
}
#[cfg(any(feature = "test-utils", test))]
pub(crate) fn has_context(&self) -> bool {
self.tbs.serialized_context.is_some()
}
pub(crate) fn epoch(&self) -> GroupEpoch {
self.tbs.epoch()
}
#[cfg(test)]
pub(crate) fn set_epoch(&mut self, epoch: u64) {
self.tbs.epoch = epoch.into();
}
#[cfg(test)]
pub(crate) fn payload(&self) -> &MlsPlaintextTbs {
&self.tbs
}
pub(crate) fn content(&self) -> &MlsPlaintextContentType {
&self.tbs.payload
}
pub(crate) fn wire_format(&self) -> WireFormat {
self.tbs.wire_format
}
pub(crate) fn membership_tag(&self) -> &Option<MembershipTag> {
&self.membership_tag
}
#[cfg(test)]
pub(crate) fn set_membership_tag(&mut self, tag: MembershipTag) {
self.membership_tag = Some(tag);
}
#[cfg(test)]
pub(crate) fn unset_membership_tag(&mut self) {
self.membership_tag = None;
}
pub(crate) fn confirmation_tag(&self) -> Option<&ConfirmationTag> {
self.confirmation_tag.as_ref()
}
#[cfg(test)]
pub(crate) fn set_confirmation_tag(&mut self, confirmation_tag: Option<ConfirmationTag>) {
self.confirmation_tag = confirmation_tag;
}
pub(crate) fn content_type(&self) -> ContentType {
self.tbs.content_type
}
#[cfg(test)]
pub(crate) fn set_content_type(&mut self, content_type: ContentType) {
self.tbs.content_type = content_type;
}
#[cfg(test)]
pub(crate) fn set_content(&mut self, content: MlsPlaintextContentType) {
self.tbs.payload = content;
}
#[cfg(test)]
pub(crate) fn signature(&self) -> &Signature {
&self.signature
}
#[cfg(test)]
pub(crate) fn set_signature(&mut self, signature: Signature) {
self.signature = signature;
}
#[cfg(test)]
pub(crate) fn invalidate_signature(&mut self) {
let mut modified_signature = self.signature().as_slice().to_vec();
modified_signature[0] ^= 0xFF;
self.signature.modify(&modified_signature);
}
}
impl Signable for MlsPlaintextTbs {
type SignedOutput = MlsPlaintext;
fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
self.tls_serialize_detached()
}
}
impl MlsPlaintextTbs {
pub(crate) fn new(
wire_format: WireFormat,
group_id: GroupId,
epoch: impl Into<GroupEpoch>,
sender: Sender,
authenticated_data: TlsByteVecU32,
payload: Payload,
) -> Self {
MlsPlaintextTbs {
serialized_context: None,
wire_format,
group_id,
epoch: epoch.into(),
sender,
authenticated_data,
content_type: payload.content_type,
payload: payload.payload,
}
}
pub(crate) fn with_context(mut self, serialized_context: Vec<u8>) -> Self {
self.serialized_context = Some(serialized_context);
self
}
fn from_plaintext(mls_plaintext: MlsPlaintext) -> Self {
MlsPlaintextTbs {
wire_format: mls_plaintext.wire_format,
serialized_context: None,
group_id: mls_plaintext.group_id,
epoch: mls_plaintext.epoch,
sender: mls_plaintext.sender,
authenticated_data: mls_plaintext.authenticated_data,
content_type: mls_plaintext.content_type,
payload: mls_plaintext.content,
}
}
pub(crate) fn epoch(&self) -> GroupEpoch {
self.epoch
}
}
impl Verifiable for VerifiableMlsPlaintext {
fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
self.tbs.tls_serialize_detached()
}
fn signature(&self) -> &Signature {
&self.signature
}
}
mod private_mod {
#[derive(Default)]
pub(crate) struct Seal;
}
impl VerifiedStruct<VerifiableMlsPlaintext> for MlsPlaintext {
fn from_verifiable(v: VerifiableMlsPlaintext, _seal: Self::SealingType) -> Self {
Self {
wire_format: v.tbs.wire_format,
group_id: v.tbs.group_id,
epoch: v.tbs.epoch,
sender: v.tbs.sender,
authenticated_data: v.tbs.authenticated_data,
content_type: v.tbs.content_type,
content: v.tbs.payload,
signature: v.signature,
confirmation_tag: v.confirmation_tag,
membership_tag: v.membership_tag,
}
}
type SealingType = private_mod::Seal;
}
impl SignedStruct<MlsPlaintextTbs> for MlsPlaintext {
fn from_payload(tbs: MlsPlaintextTbs, signature: Signature) -> Self {
Self {
wire_format: tbs.wire_format,
group_id: tbs.group_id,
epoch: tbs.epoch,
sender: tbs.sender,
authenticated_data: tbs.authenticated_data,
content_type: tbs.content_type,
content: tbs.payload,
signature,
confirmation_tag: None,
membership_tag: None,
}
}
}
#[derive(TlsSerialize, TlsSize)]
pub(crate) struct MlsPlaintextCommitContent<'a> {
pub(super) wire_format: WireFormat,
pub(super) group_id: &'a GroupId,
pub(super) epoch: GroupEpoch,
pub(super) sender: &'a Sender,
pub(super) authenticated_data: &'a TlsByteVecU32,
pub(super) content_type: ContentType,
pub(super) commit: &'a Commit,
pub(super) signature: &'a Signature,
}
impl<'a> TryFrom<&'a MlsPlaintext> for MlsPlaintextCommitContent<'a> {
type Error = &'static str;
fn try_from(mls_plaintext: &'a MlsPlaintext) -> Result<Self, Self::Error> {
let commit = match &mls_plaintext.content {
MlsPlaintextContentType::Commit(commit) => commit,
_ => return Err("MlsPlaintext needs to contain a Commit."),
};
Ok(MlsPlaintextCommitContent {
wire_format: mls_plaintext.wire_format,
group_id: &mls_plaintext.group_id,
epoch: mls_plaintext.epoch,
sender: &mls_plaintext.sender,
authenticated_data: &mls_plaintext.authenticated_data,
content_type: mls_plaintext.content_type,
commit,
signature: &mls_plaintext.signature,
})
}
}
#[derive(TlsSerialize, TlsSize)]
pub(crate) struct MlsPlaintextCommitAuthData<'a> {
pub(crate) confirmation_tag: Option<&'a ConfirmationTag>,
}
impl<'a> TryFrom<&'a MlsPlaintext> for MlsPlaintextCommitAuthData<'a> {
type Error = &'static str;
fn try_from(mls_plaintext: &'a MlsPlaintext) -> Result<Self, Self::Error> {
match mls_plaintext.confirmation_tag.as_ref() {
Some(confirmation_tag) => Ok(MlsPlaintextCommitAuthData {
confirmation_tag: Some(confirmation_tag),
}),
None => Err("MLSPlaintext needs to contain a confirmation tag."),
}
}
}
impl<'a> From<&'a ConfirmationTag> for MlsPlaintextCommitAuthData<'a> {
fn from(confirmation_tag: &'a ConfirmationTag) -> Self {
MlsPlaintextCommitAuthData {
confirmation_tag: Some(confirmation_tag),
}
}
}