mod apply_proposals;
mod new_from_welcome;
mod validation;
pub(crate) mod create_commit;
pub(crate) mod create_commit_params;
pub(crate) mod new_from_external_init;
pub(crate) mod past_secrets;
pub(crate) mod process;
pub(crate) mod proposals;
pub(crate) mod staged_commit;
#[cfg(test)]
mod test_core_group;
#[cfg(test)]
mod test_create_commit_params;
#[cfg(test)]
mod test_duplicate_extension;
#[cfg(test)]
mod test_external_init;
#[cfg(test)]
mod test_past_secrets;
#[cfg(test)]
mod test_proposals;
#[cfg(test)]
use super::errors::CreateGroupContextExtProposalError;
use crate::{
ciphersuite::{hash_ref::KeyPackageRef, signable::*},
credentials::*,
error::LibraryError,
extensions::errors::*,
framing::*,
group::*,
key_packages::{errors::KeyPackageExtensionSupportError, *},
messages::{proposals::*, public_group_state::*, *},
schedule::{message_secrets::*, psk::*, *},
tree::{secret_tree::SecretTreeError, sender_ratchet::SenderRatchetConfiguration},
treesync::{errors::TreeSyncError, *},
versions::ProtocolVersion,
};
use self::{past_secrets::MessageSecretsStore, staged_commit::StagedCommit};
use log::{debug, trace};
use openmls_traits::{crypto::OpenMlsCrypto, types::Ciphersuite};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use std::convert::TryFrom;
#[cfg(test)]
use std::io::{Error, Read, Write};
use tls_codec::Serialize as TlsSerializeTrait;
use super::{
errors::{CoreGroupBuildError, CreateAddProposalError, ExporterError, ProposalValidationError},
group_context::*,
};
#[derive(Debug)]
pub(crate) struct CreateCommitResult {
pub(crate) commit: MlsPlaintext,
pub(crate) welcome_option: Option<Welcome>,
pub(crate) staged_commit: StagedCommit,
}
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct CoreGroup {
ciphersuite: Ciphersuite,
group_context: GroupContext,
group_epoch_secrets: GroupEpochSecrets,
tree: TreeSync,
interim_transcript_hash: Vec<u8>,
use_ratchet_tree_extension: bool,
mls_version: ProtocolVersion,
message_secrets_store: MessageSecretsStore,
}
pub(crate) struct CoreGroupBuilder {
key_package_bundle: KeyPackageBundle,
group_id: GroupId,
config: Option<CoreGroupConfig>,
psk_ids: Vec<PreSharedKeyId>,
version: Option<ProtocolVersion>,
required_capabilities: Option<RequiredCapabilitiesExtension>,
max_past_epochs: usize,
}
impl CoreGroupBuilder {
pub(crate) fn new(group_id: GroupId, key_package_bundle: KeyPackageBundle) -> Self {
Self {
key_package_bundle,
group_id,
config: None,
psk_ids: vec![],
version: None,
required_capabilities: None,
max_past_epochs: 0,
}
}
pub(crate) fn with_config(mut self, config: CoreGroupConfig) -> Self {
self.config = Some(config);
self
}
#[cfg(test)]
pub(crate) fn with_psk(mut self, psk_ids: Vec<PreSharedKeyId>) -> Self {
self.psk_ids = psk_ids;
self
}
pub(crate) fn with_required_capabilities(
mut self,
required_capabilities: RequiredCapabilitiesExtension,
) -> Self {
self.required_capabilities = Some(required_capabilities);
self
}
pub fn with_max_past_epoch_secrets(mut self, max_past_epochs: usize) -> Self {
self.max_past_epochs = max_past_epochs;
self
}
pub(crate) fn build(
self,
backend: &impl OpenMlsCryptoProvider,
) -> Result<CoreGroup, CoreGroupBuildError> {
let ciphersuite = self.key_package_bundle.key_package().ciphersuite();
let config = self.config.unwrap_or_default();
let required_capabilities = self.required_capabilities.unwrap_or_default();
let version = self.version.unwrap_or_default();
debug!("Created group {:x?}", self.group_id);
trace!(" >>> with {:?}, {:?}", ciphersuite, config);
let (tree, commit_secret) = TreeSync::new(backend, self.key_package_bundle)?;
required_capabilities.check_support().map_err(|e| match e {
ExtensionError::UnsupportedProposalType => CoreGroupBuildError::UnsupportedProposalType,
ExtensionError::UnsupportedExtensionType => {
CoreGroupBuildError::UnsupportedExtensionType
}
_ => LibraryError::custom("Unexpected ExtensionError").into(),
})?;
let required_capabilities = &[Extension::RequiredCapabilities(required_capabilities)];
let group_context = GroupContext::create_initial_group_context(
ciphersuite,
self.group_id,
tree.tree_hash().to_vec(),
required_capabilities,
);
let joiner_secret = JoinerSecret::new(
backend,
commit_secret,
&InitSecret::random(ciphersuite, backend, version)
.map_err(LibraryError::unexpected_crypto_error)?,
)
.map_err(LibraryError::unexpected_crypto_error)?;
let serialized_group_context = group_context
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?;
let psk_secret = PskSecret::new(ciphersuite, backend, &self.psk_ids)?;
let mut key_schedule = KeySchedule::init(ciphersuite, backend, joiner_secret, psk_secret)?;
key_schedule
.add_context(backend, &serialized_group_context)
.map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
let epoch_secrets = key_schedule
.epoch_secrets(backend)
.map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
let (group_epoch_secrets, message_secrets) =
epoch_secrets.split_secrets(serialized_group_context, 1u32, 0u32);
let message_secrets_store =
MessageSecretsStore::new_with_secret(self.max_past_epochs, message_secrets);
let interim_transcript_hash = vec![];
Ok(CoreGroup {
ciphersuite,
group_context,
group_epoch_secrets,
tree,
interim_transcript_hash,
use_ratchet_tree_extension: config.add_ratchet_tree_extension,
mls_version: version,
message_secrets_store,
})
}
}
impl CoreGroup {
pub(crate) fn builder(
group_id: GroupId,
key_package_bundle: KeyPackageBundle,
) -> CoreGroupBuilder {
CoreGroupBuilder::new(group_id, key_package_bundle)
}
pub(crate) fn create_add_proposal(
&self,
framing_parameters: FramingParameters,
credential_bundle: &CredentialBundle,
joiner_key_package: KeyPackage,
backend: &impl OpenMlsCryptoProvider,
) -> Result<MlsPlaintext, CreateAddProposalError> {
joiner_key_package
.validate_required_capabilities(self.required_capabilities())
.map_err(|e| match e {
KeyPackageExtensionSupportError::UnsupportedExtension => {
CreateAddProposalError::UnsupportedExtensions
}
})?;
let add_proposal = AddProposal {
key_package: joiner_key_package,
};
let proposal = Proposal::Add(add_proposal);
MlsPlaintext::member_proposal(
framing_parameters,
self.key_package_ref()
.ok_or_else(|| LibraryError::custom("missing key package"))?,
proposal,
credential_bundle,
self.context(),
self.message_secrets().membership_key(),
backend,
)
.map_err(|e| e.into())
}
pub(crate) fn create_update_proposal(
&self,
framing_parameters: FramingParameters,
credential_bundle: &CredentialBundle,
key_package: KeyPackage,
backend: &impl OpenMlsCryptoProvider,
) -> Result<MlsPlaintext, LibraryError> {
let update_proposal = UpdateProposal { key_package };
let proposal = Proposal::Update(update_proposal);
MlsPlaintext::member_proposal(
framing_parameters,
self.key_package_ref()
.ok_or_else(|| LibraryError::custom("missing key package"))?,
proposal,
credential_bundle,
self.context(),
self.message_secrets().membership_key(),
backend,
)
}
pub(crate) fn create_remove_proposal(
&self,
framing_parameters: FramingParameters,
credential_bundle: &CredentialBundle,
removed: &KeyPackageRef,
backend: &impl OpenMlsCryptoProvider,
) -> Result<MlsPlaintext, LibraryError> {
let remove_proposal = RemoveProposal { removed: *removed };
let proposal = Proposal::Remove(remove_proposal);
MlsPlaintext::member_proposal(
framing_parameters,
self.key_package_ref()
.ok_or_else(|| LibraryError::custom("missing key package"))?,
proposal,
credential_bundle,
self.context(),
self.message_secrets().membership_key(),
backend,
)
}
#[cfg(test)]
pub(crate) fn create_presharedkey_proposal(
&self,
framing_parameters: FramingParameters,
credential_bundle: &CredentialBundle,
psk: PreSharedKeyId,
backend: &impl OpenMlsCryptoProvider,
) -> Result<MlsPlaintext, LibraryError> {
let presharedkey_proposal = PreSharedKeyProposal::new(psk);
let proposal = Proposal::PreSharedKey(presharedkey_proposal);
MlsPlaintext::member_proposal(
framing_parameters,
self.key_package_ref()
.ok_or_else(|| LibraryError::custom("missing key package"))?,
proposal,
credential_bundle,
self.context(),
self.message_secrets().membership_key(),
backend,
)
}
#[cfg(test)]
pub(crate) fn create_group_context_ext_proposal(
&self,
framing_parameters: FramingParameters,
credential_bundle: &CredentialBundle,
extensions: &[Extension],
backend: &impl OpenMlsCryptoProvider,
) -> Result<MlsPlaintext, CreateGroupContextExtProposalError> {
let required_extension = extensions
.iter()
.find(|extension| extension.extension_type() == ExtensionType::RequiredCapabilities);
if let Some(required_extension) = required_extension {
let required_capabilities = required_extension.as_required_capabilities_extension()?;
required_capabilities.check_support()?;
self.treesync()
.own_leaf_node()
.map_err(|_| LibraryError::custom("Expected own leaf"))?
.key_package()
.validate_required_capabilities(required_capabilities)?;
for (_index, key_package) in self.treesync().full_leaves()? {
key_package.check_extension_support(required_capabilities.extensions())?;
}
}
let proposal = GroupContextExtensionProposal::new(extensions);
let proposal = Proposal::GroupContextExtensions(proposal);
MlsPlaintext::member_proposal(
framing_parameters,
self.key_package_ref()
.ok_or_else(|| LibraryError::custom("missing key package"))?,
proposal,
credential_bundle,
self.context(),
self.message_secrets().membership_key(),
backend,
)
.map_err(|e| e.into())
}
pub(crate) fn create_application_message(
&mut self,
aad: &[u8],
msg: &[u8],
credential_bundle: &CredentialBundle,
padding_size: usize,
backend: &impl OpenMlsCryptoProvider,
) -> Result<MlsCiphertext, MessageEncryptionError> {
let mls_plaintext = MlsPlaintext::new_application(
self.key_package_ref()
.ok_or_else(|| LibraryError::custom("missing key package"))?,
aad,
msg,
credential_bundle,
self.context(),
self.message_secrets().membership_key(),
backend,
)?;
self.encrypt(mls_plaintext, padding_size, backend)
}
pub(crate) fn encrypt(
&mut self,
mls_plaintext: MlsPlaintext,
padding_size: usize,
backend: &impl OpenMlsCryptoProvider,
) -> Result<MlsCiphertext, MessageEncryptionError> {
log::trace!("{:?}", mls_plaintext.confirmation_tag());
MlsCiphertext::try_from_plaintext(
&mls_plaintext,
self.ciphersuite,
backend,
MlsMessageHeader {
group_id: self.group_id().clone(),
epoch: self.context().epoch(),
sender: crate::tree::index::SecretTreeLeafIndex(self.own_leaf_index()),
},
self.message_secrets_store.message_secrets_mut(),
padding_size,
)
}
#[cfg(any(feature = "test-utils", test))]
pub(crate) fn decrypt(
&mut self,
mls_ciphertext: &MlsCiphertext,
backend: &impl OpenMlsCryptoProvider,
sender_ratchet_configuration: &SenderRatchetConfiguration,
) -> Result<VerifiableMlsPlaintext, MessageDecryptionError> {
let ciphersuite = self.ciphersuite();
let message_secrets = self
.message_secrets_mut(mls_ciphertext.epoch())
.map_err(|_| MessageDecryptionError::AeadError)?;
let sender_data = mls_ciphertext.sender_data(message_secrets, backend, ciphersuite)?;
let sender_index = self
.sender_index(&sender_data.sender)
.map_err(|_| MessageDecryptionError::SenderError(SenderError::UnknownSender))?;
let sender_index = crate::tree::index::SecretTreeLeafIndex(sender_index);
let message_secrets = self
.message_secrets_mut(mls_ciphertext.epoch())
.map_err(|_| MessageDecryptionError::AeadError)?;
mls_ciphertext.to_plaintext(
ciphersuite,
backend,
message_secrets,
sender_index,
sender_ratchet_configuration,
sender_data,
)
}
pub(crate) fn export_secret(
&self,
backend: &impl OpenMlsCryptoProvider,
label: &str,
context: &[u8],
key_length: usize,
) -> Result<Vec<u8>, ExporterError> {
if key_length > u16::MAX.into() {
log::error!("Got a key that is larger than u16::MAX");
return Err(ExporterError::KeyLengthTooLong);
}
Ok(self
.group_epoch_secrets
.exporter_secret()
.derive_exported_secret(self.ciphersuite(), backend, label, context, key_length)
.map_err(LibraryError::unexpected_crypto_error)?)
}
pub(crate) fn authentication_secret(&self) -> &AuthenticationSecret {
self.group_epoch_secrets().authentication_secret()
}
pub(crate) fn resumption_secret(&self) -> &ResumptionSecret {
self.group_epoch_secrets().resumption_secret()
}
#[cfg(test)]
pub(crate) fn load<R: Read>(reader: R) -> Result<CoreGroup, Error> {
serde_json::from_reader(reader).map_err(|e| e.into())
}
#[cfg(test)]
pub(crate) fn save<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
let serialized_core_group = serde_json::to_string_pretty(self)?;
writer.write_all(&serialized_core_group.into_bytes())
}
pub(crate) fn treesync(&self) -> &TreeSync {
&self.tree
}
pub(crate) fn ciphersuite(&self) -> Ciphersuite {
self.ciphersuite
}
pub(crate) fn version(&self) -> ProtocolVersion {
self.mls_version
}
pub(crate) fn context(&self) -> &GroupContext {
&self.group_context
}
pub(crate) fn group_id(&self) -> &GroupId {
self.group_context.group_id()
}
pub(crate) fn other_extensions(&self) -> Vec<Extension> {
vec![Extension::RatchetTree(RatchetTreeExtension::new(
self.treesync().export_nodes(),
))]
}
pub(crate) fn group_context_extensions(&self) -> &[Extension] {
self.group_context.extensions()
}
pub(crate) fn required_capabilities(&self) -> Option<&RequiredCapabilitiesExtension> {
self.group_context.required_capabilities()
}
pub(crate) fn export_public_group_state(
&self,
backend: &impl OpenMlsCryptoProvider,
credential_bundle: &CredentialBundle,
) -> Result<PublicGroupState, LibraryError> {
let pgs_tbs = PublicGroupStateTbs::new(backend, self)?;
pgs_tbs.sign(backend, credential_bundle)
}
#[cfg(test)]
pub(crate) fn use_ratchet_tree_extension(&self) -> bool {
self.use_ratchet_tree_extension
}
}
impl CoreGroup {
pub(crate) fn sender_index(
&self,
key_package_ref: &KeyPackageRef,
) -> Result<u32, TreeSyncError> {
self.treesync().leaf_index(key_package_ref)
}
pub(crate) fn own_leaf_index(&self) -> u32 {
self.treesync().own_leaf_index()
}
pub(crate) fn key_package_ref(&self) -> Option<&KeyPackageRef> {
self.treesync()
.own_leaf_node()
.ok()
.and_then(|node| node.key_package_ref())
}
pub(crate) fn group_epoch_secrets(&self) -> &GroupEpochSecrets {
&self.group_epoch_secrets
}
pub(crate) fn message_secrets(&self) -> &MessageSecrets {
self.message_secrets_store.message_secrets()
}
pub(crate) fn set_max_past_epochs(&mut self, max_past_epochs: usize) {
self.message_secrets_store.resize(max_past_epochs);
}
pub(crate) fn message_secrets_mut<'secret, 'group: 'secret>(
&'group mut self,
epoch: GroupEpoch,
) -> Result<&'secret mut MessageSecrets, SecretTreeError> {
if epoch < self.context().epoch() {
self.message_secrets_store
.secrets_for_epoch_mut(epoch)
.ok_or(SecretTreeError::TooDistantInThePast)
} else {
Ok(self.message_secrets_store.message_secrets_mut())
}
}
pub(crate) fn message_secrets_and_leaves_mut<'secret, 'group: 'secret>(
&'group mut self,
epoch: GroupEpoch,
) -> Result<(&'secret mut MessageSecrets, IndexedKeyPackageRefs), MessageDecryptionError> {
if epoch < self.context().epoch() {
self.message_secrets_store
.secrets_and_leaves_for_epoch_mut(epoch)
.ok_or({
MessageDecryptionError::SecretTreeError(SecretTreeError::TooDistantInThePast)
})
} else {
Ok((self.message_secrets_store.message_secrets_mut(), Vec::new()))
}
}
#[cfg(any(feature = "test-utils", test))]
pub(crate) fn message_secrets_test_mut(&mut self) -> &mut MessageSecrets {
self.message_secrets_store.message_secrets_mut()
}
pub(crate) fn interim_transcript_hash(&self) -> &[u8] {
&self.interim_transcript_hash
}
pub(crate) fn confirmed_transcript_hash(&self) -> &[u8] {
self.group_context.confirmed_transcript_hash()
}
#[cfg(any(feature = "test-utils", test))]
pub(crate) fn context_mut(&mut self) -> &mut GroupContext {
&mut self.group_context
}
#[cfg(any(feature = "test-utils", test))]
pub(crate) fn print_tree(&self, message: &str) {
use super::tests::tree_printing::print_tree;
print_tree(self, message);
}
}
pub(crate) fn update_confirmed_transcript_hash(
ciphersuite: Ciphersuite,
backend: &impl OpenMlsCryptoProvider,
mls_plaintext_commit_content: &MlsPlaintextCommitContent,
interim_transcript_hash: &[u8],
) -> Result<Vec<u8>, LibraryError> {
let commit_content_bytes = mls_plaintext_commit_content
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?;
backend
.crypto()
.hash(
ciphersuite.hash_algorithm(),
&[interim_transcript_hash, &commit_content_bytes].concat(),
)
.map_err(LibraryError::unexpected_crypto_error)
}
pub(crate) fn update_interim_transcript_hash(
ciphersuite: Ciphersuite,
backend: &impl OpenMlsCryptoProvider,
mls_plaintext_commit_auth_data: &MlsPlaintextCommitAuthData,
confirmed_transcript_hash: &[u8],
) -> Result<Vec<u8>, LibraryError> {
let commit_auth_data_bytes = mls_plaintext_commit_auth_data
.tls_serialize_detached()
.map_err(LibraryError::missing_bound_check)?;
backend
.crypto()
.hash(
ciphersuite.hash_algorithm(),
&[confirmed_transcript_hash, &commit_auth_data_bytes].concat(),
)
.map_err(LibraryError::unexpected_crypto_error)
}
#[derive(Clone, Copy, Default, Debug)]
pub(crate) struct CoreGroupConfig {
pub(crate) add_ratchet_tree_extension: bool,
}
pub(crate) type IndexedKeyPackageRefs = Vec<(u32, KeyPackageRef)>;