#![allow(clippy::type_complexity)]
use std::collections::{HashSet, VecDeque};
use std::marker::PhantomData;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::crypto::xchacha20::{XAeadError, XAeadNonce};
use crate::crypto::{Rng, RngError};
use crate::data_scheme::data::{decrypt_data, encrypt_data};
use crate::data_scheme::dcgka::{
ControlMessage, Dcgka, DcgkaError, DcgkaState, DirectMessage, GroupSecretOutput,
OperationOutput, ProcessInput,
};
use crate::data_scheme::group_secret::{
GroupSecret, GroupSecretError, GroupSecretId, SecretBundle, SecretBundleState,
};
use crate::key_bundle::LongTermKeyBundle;
use crate::traits::{
GroupMembership, GroupMessage, GroupMessageContent, IdentityHandle, IdentityManager,
IdentityRegistry, OperationId, Ordering, PreKeyManager, PreKeyRegistry,
};
pub struct EncryptionGroup<ID, OP, PKI, DGM, KMG, ORD> {
_marker: PhantomData<(ID, OP, PKI, DGM, KMG, ORD)>,
}
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
pub struct GroupState<ID, OP, PKI, DGM, KMG, ORD>
where
ID: IdentityHandle,
OP: OperationId,
PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
PKI::State: Clone,
DGM: GroupMembership<ID, OP>,
KMG: IdentityManager<KMG::State> + PreKeyManager,
KMG::State: Clone,
ORD: Ordering<ID, OP, DGM>,
{
pub my_id: ID,
pub dcgka: DcgkaState<ID, OP, PKI, DGM, KMG>,
pub orderer: ORD::State,
pub secrets: SecretBundleState,
pub is_welcomed: bool,
}
impl<ID, OP, PKI, DGM, KMG, ORD> EncryptionGroup<ID, OP, PKI, DGM, KMG, ORD>
where
ID: IdentityHandle,
OP: OperationId,
PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
PKI::State: Clone,
DGM: GroupMembership<ID, OP>,
KMG: IdentityManager<KMG::State> + PreKeyManager,
KMG::State: Clone,
ORD: Ordering<ID, OP, DGM>,
{
#[allow(unused)]
pub fn init(
my_id: ID,
my_keys: KMG::State,
pki: PKI::State,
dgm: DGM::State,
orderer: ORD::State,
) -> GroupState<ID, OP, PKI, DGM, KMG, ORD> {
GroupState {
my_id,
dcgka: Dcgka::init(my_id, my_keys, pki, dgm),
orderer,
secrets: SecretBundle::init(),
is_welcomed: false,
}
}
pub fn create(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
initial_members: Vec<ID>,
rng: &Rng,
) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
if y.is_welcomed {
return Err(GroupError::GroupAlreadyEstablished);
}
let group_secret = SecretBundle::generate(&y.secrets, rng)?;
let (y_dcgka_i, pre) = Dcgka::create(y.dcgka, initial_members, &group_secret, rng)?;
y.dcgka = y_dcgka_i;
let (mut y_i, message) = Self::process_local(y, pre, Some(group_secret))?;
let y_orderer_i = ORD::set_welcome(y_i.orderer, &message).map_err(GroupError::Orderer)?;
y_i.orderer = y_orderer_i;
y_i.is_welcomed = true;
Ok((y_i, message))
}
pub fn add(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
added: ID,
rng: &Rng,
) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
if !y.is_welcomed {
return Err(GroupError::GroupNotYetEstablished);
}
if y.my_id == added {
return Err(GroupError::NotAddOurselves);
}
let (y_dcgka_i, pre) = Dcgka::add(y.dcgka, added, &y.secrets, rng)?;
y.dcgka = y_dcgka_i;
Self::process_local(y, pre, None)
}
pub fn remove(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
removed: ID,
rng: &Rng,
) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
if !y.is_welcomed {
return Err(GroupError::GroupNotYetEstablished);
}
let group_secret = SecretBundle::generate(&y.secrets, rng)?;
let (y_dcgka_i, pre) = Dcgka::remove(y.dcgka, removed, &group_secret, rng)?;
y.dcgka = y_dcgka_i;
Self::process_local(y, pre, Some(group_secret))
}
pub fn update(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
rng: &Rng,
) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
if !y.is_welcomed {
return Err(GroupError::GroupNotYetEstablished);
}
let group_secret = SecretBundle::generate(&y.secrets, rng)?;
let (y_dcgka_i, pre) = Dcgka::update(y.dcgka, &group_secret, rng)?;
y.dcgka = y_dcgka_i;
Self::process_local(y, pre, Some(group_secret))
}
pub fn receive(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
message: &ORD::Message,
) -> GroupResult<Vec<GroupOutput<ID, OP, DGM, ORD>>, ID, OP, PKI, DGM, KMG, ORD> {
let message_content = message.content();
let mut is_create_or_welcome = false;
if let GroupMessageContent::Control(ControlMessage::Create {
ref initial_members,
}) = message_content
{
if y.is_welcomed {
return Err(GroupError::GroupAlreadyEstablished);
}
if initial_members.contains(&y.my_id) {
is_create_or_welcome = true;
}
}
if let GroupMessageContent::Control(ControlMessage::Add { added }) = message_content
&& !y.is_welcomed
&& added == y.my_id
{
is_create_or_welcome = true;
}
let y_orderer_i = ORD::queue(y.orderer, message).map_err(GroupError::Orderer)?;
y.orderer = y_orderer_i;
if !y.is_welcomed && !is_create_or_welcome {
return Ok((y, vec![]));
}
if !y.is_welcomed && is_create_or_welcome {
let y_orderer_i = ORD::set_welcome(y.orderer, message).map_err(GroupError::Orderer)?;
y.orderer = y_orderer_i;
}
let mut results = Vec::new();
let mut y_loop = y;
let mut control_messages = VecDeque::new();
let mut application_messages = VecDeque::new();
loop {
let (y_orderer_next, result) =
ORD::next_ready_message(y_loop.orderer).map_err(GroupError::Orderer)?;
y_loop.orderer = y_orderer_next;
let Some(message) = result else {
break;
};
match message.content() {
GroupMessageContent::Control(_) => {
control_messages.push_back(message);
}
GroupMessageContent::Application { .. } => {
application_messages.push_back(message);
}
}
}
while let Some(message) = control_messages.pop_front() {
let (y_next, result) = Self::process_ready(y_loop, &message)?;
y_loop = y_next;
if let Some(message) = result {
results.push(message);
}
}
while let Some(message) = application_messages.pop_front() {
let (y_next, result) = Self::process_ready(y_loop, &message)?;
y_loop = y_next;
if let Some(message) = result {
results.push(message);
}
}
Ok((y_loop, results))
}
pub fn send(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
plaintext: &[u8],
rng: &Rng,
) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
if !y.is_welcomed {
return Err(GroupError::GroupNotYetEstablished);
}
let Some(group_secret) = y.secrets.latest() else {
return Err(GroupError::NoGroupSecretAvailable);
};
let secret_id = group_secret.id();
let (nonce, ciphertext) = Self::encrypt(group_secret, plaintext, rng)?;
let (y_orderer_i, message) =
ORD::next_application_message(y.orderer, secret_id, nonce, ciphertext)
.map_err(GroupError::Orderer)?;
y.orderer = y_orderer_i;
Ok((y, message))
}
pub fn members(
y: &GroupState<ID, OP, PKI, DGM, KMG, ORD>,
) -> Result<HashSet<ID>, GroupError<ID, OP, PKI, DGM, KMG, ORD>> {
let members = Dcgka::members(&y.dcgka)?;
Ok(members)
}
#[allow(unused)]
pub fn update_secrets<F>(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
update_fn: F,
) -> GroupState<ID, OP, PKI, DGM, KMG, ORD>
where
F: FnOnce(SecretBundleState) -> SecretBundleState,
{
y.secrets = update_fn(y.secrets);
y
}
fn process_local(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
output: OperationOutput<ID, OP, DGM>,
group_secret: Option<GroupSecret>,
) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
let (y_orderer_i, message) =
ORD::next_control_message(y.orderer, &output.control_message, &output.direct_messages)
.map_err(GroupError::Orderer)?;
y.orderer = y_orderer_i;
let (y_dcgka_i, _) = Dcgka::process(
y.dcgka,
ProcessInput {
seq: message.id(),
sender: message.sender(),
control_message: output.control_message,
direct_message: None,
},
)?;
y.dcgka = y_dcgka_i;
if let Some(group_secret) = group_secret {
y.secrets = SecretBundle::insert(y.secrets, group_secret);
}
Ok((y, message))
}
fn process_ready(
y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
message: &ORD::Message,
) -> GroupResult<Option<GroupOutput<ID, OP, DGM, ORD>>, ID, OP, PKI, DGM, KMG, ORD> {
match message.content() {
GroupMessageContent::Control(control_message) => {
let direct_message = message
.direct_messages()
.into_iter()
.find(|dm| dm.recipient == y.my_id);
let (mut y_i, output) = Self::process_remote(
y,
message.id(),
message.sender(),
control_message,
direct_message,
)?;
let we_are_members = Self::members(&y_i)?.contains(&y_i.my_id);
if !y_i.is_welcomed && we_are_members {
y_i.is_welcomed = true;
}
let is_removed = y_i.is_welcomed && !we_are_members;
if is_removed {
Ok((y_i, Some(GroupOutput::Removed)))
} else {
Ok((y_i, output.map(|msg| GroupOutput::Control(msg))))
}
}
GroupMessageContent::Application {
group_secret_id,
ciphertext,
nonce,
} => {
let (y_i, plaintext) = Self::decrypt(y, nonce, group_secret_id, ciphertext)?;
Ok((y_i, Some(GroupOutput::Application { plaintext })))
}
}
}
fn process_remote(
mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
seq: OP,
sender: ID,
control_message: ControlMessage<ID>,
direct_message: Option<DirectMessage<ID, OP, DGM>>,
) -> GroupResult<Option<ORD::Message>, ID, OP, PKI, DGM, KMG, ORD> {
let (y_dcgka_i, output) = Dcgka::process(
y.dcgka,
ProcessInput {
seq,
sender,
control_message,
direct_message,
},
)?;
y.dcgka = y_dcgka_i;
y.secrets = match output {
GroupSecretOutput::Secret(group_secret) => {
SecretBundle::insert(y.secrets, group_secret)
}
GroupSecretOutput::Bundle(secret_bundle_state) => {
SecretBundle::extend(y.secrets, secret_bundle_state)
}
GroupSecretOutput::None => y.secrets,
};
Ok((y, None))
}
fn encrypt(
group_secret: &GroupSecret,
plaintext: &[u8],
rng: &Rng,
) -> Result<(XAeadNonce, Vec<u8>), GroupError<ID, OP, PKI, DGM, KMG, ORD>> {
let nonce: XAeadNonce = rng.random_array()?;
let ciphertext = encrypt_data(plaintext, group_secret, nonce)?;
Ok((nonce, ciphertext))
}
fn decrypt(
y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
nonce: XAeadNonce,
group_secret_id: GroupSecretId,
ciphertext: Vec<u8>,
) -> GroupResult<Vec<u8>, ID, OP, PKI, DGM, KMG, ORD> {
let Some(group_secret) = y.secrets.get(&group_secret_id) else {
return Err(GroupError::UnknownGroupSecret(hex::encode(group_secret_id)));
};
let plaintext = decrypt_data(&ciphertext, group_secret, nonce)?;
Ok((y, plaintext))
}
}
pub type GroupResult<T, ID, OP, PKI, DGM, KMG, ORD> =
Result<(GroupState<ID, OP, PKI, DGM, KMG, ORD>, T), GroupError<ID, OP, PKI, DGM, KMG, ORD>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GroupOutput<ID, OP, DGM, ORD>
where
DGM: GroupMembership<ID, OP>,
ORD: Ordering<ID, OP, DGM>,
{
Control(ORD::Message),
Application { plaintext: Vec<u8> },
Removed,
}
#[derive(Debug, Error)]
pub enum GroupError<ID, OP, PKI, DGM, KMG, ORD>
where
PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
DGM: GroupMembership<ID, OP>,
KMG: PreKeyManager,
ORD: Ordering<ID, OP, DGM>,
{
#[error(transparent)]
Rng(#[from] RngError),
#[error(transparent)]
Dcgka(#[from] DcgkaError<ID, OP, PKI, DGM, KMG>),
#[error(transparent)]
Orderer(ORD::Error),
#[error(transparent)]
XAead(#[from] XAeadError),
#[error(transparent)]
GroupSecret(#[from] GroupSecretError),
#[error("creating or joining a group is not possible, state is already established")]
GroupAlreadyEstablished,
#[error("state is not ready yet, group needs to be created or joined first")]
GroupNotYetEstablished,
#[error("can not add ourselves to the group")]
NotAddOurselves,
#[error("we do not have created or learned about any group secrets yet")]
NoGroupSecretAvailable,
#[error("tried to decrypt message with an unknown group secret: {0}")]
UnknownGroupSecret(String),
}
#[cfg(test)]
mod tests {
use crate::crypto::Rng;
use crate::data_scheme::group::GroupOutput;
use crate::data_scheme::test_utils::network::init_group_state;
use crate::traits::{GroupMembership, Ordering};
use super::{EncryptionGroup, GroupError};
pub fn assert_payload<ID, OP, DGM, ORD>(
messages: &[GroupOutput<ID, OP, DGM, ORD>],
expected_payload: &[u8],
) where
DGM: GroupMembership<ID, OP>,
ORD: Ordering<ID, OP, DGM>,
{
let message = messages.first().expect("expected at least one message");
if let GroupOutput::Application { plaintext } = message {
assert_eq!(
plaintext, expected_payload,
"expected payload does not match"
);
} else {
panic!("expected application message");
}
}
#[test]
fn post_compromise_security() {
let rng = Rng::from_seed([1; 32]);
let alice = 0;
let bob = 1;
let charlie = 2;
let [y_alice, y_bob, y_charlie] = init_group_state([alice, bob, charlie], &rng);
let (y_alice, alice_message_0) =
EncryptionGroup::create(y_alice, vec![alice, bob, charlie], &rng).unwrap();
let (y_bob, _) = EncryptionGroup::receive(y_bob, &alice_message_0).unwrap();
let (y_charlie, _) = EncryptionGroup::receive(y_charlie, &alice_message_0).unwrap();
let (y_alice, alice_message_1) = EncryptionGroup::send(y_alice, b"Da Da Da", &rng).unwrap();
let (y_bob, bob_output) = EncryptionGroup::receive(y_bob, &alice_message_1).unwrap();
assert_payload(&bob_output, b"Da Da Da");
let (y_charlie, charlie_output) =
EncryptionGroup::receive(y_charlie, &alice_message_1).unwrap();
assert_payload(&charlie_output, b"Da Da Da");
let (y_bob, bob_message_0) = EncryptionGroup::remove(y_bob, charlie, &rng).unwrap();
let (y_alice, alice_output) = EncryptionGroup::receive(y_alice, &bob_message_0).unwrap();
assert!(alice_output.is_empty());
assert_eq!(
y_alice.secrets.latest().unwrap().id(),
y_bob.secrets.latest().unwrap().id()
);
let (y_charlie, charlie_output) =
EncryptionGroup::receive(y_charlie, &bob_message_0).unwrap();
let GroupOutput::Removed = charlie_output.first().unwrap() else {
panic!("expected removed output");
};
let (_y_alice, alice_message_2) =
EncryptionGroup::send(y_alice, b"Ich lieb dich nicht / Du liebst mich nicht", &rng)
.unwrap();
let (_y_bob, bob_output) = EncryptionGroup::receive(y_bob, &alice_message_2).unwrap();
assert_payload(&bob_output, b"Ich lieb dich nicht / Du liebst mich nicht");
std::assert_matches!(
EncryptionGroup::receive(y_charlie, &alice_message_2),
Err(GroupError::UnknownGroupSecret(_))
);
}
}