openmls 0.8.1

A Rust implementation of the Messaging Layer Security (MLS) protocol, as defined in RFC 9420.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
//! This crate provides a framework to set up clients and groups using the
//! OpenMLS mls_group API. To use the framework, start by creating a new
//! `TestSetup` with a number of clients. After that, `create_clients` has to be
//! called before the the `TestSetup` can be used.
//!
//! Note that due to lifetime issues, no new clients can be created after
//! initialization.
//!
//! After initialization, the `TestSetup` enables the creation of groups using
//! `create_group`, which simply creates a one-member group, or
//! `create_random_group`, which creates a group of the given size with random
//! clients.
//!
//! Existing groups are represented by the `groups` field of the `TestSetup` and
//! initial members can be instructed to either propose or commit adds, removes
//! or updates via the corresponding functions of `TestSetup`. Note, that these
//! functions require a `&Group` reference, which can be obtained via the
//! `groups` field. When using these functions, the `TestSetup` fills the role
//! of the DS and automatically distributes the required KeyPackages and
//! resulting mls messages to the individual clients. Alternatively, the clients
//! can be manipulated manually via the `Client` struct, which contains their
//! group states.

use crate::storage::OpenMlsProvider;
use crate::test_utils::OpenMlsRustCrypto;
use crate::treesync::LeafNodeParameters;
use crate::{
    binary_tree::array_representation::LeafNodeIndex,
    ciphersuite::{hash_ref::KeyPackageRef, *},
    credentials::*,
    framing::*,
    group::*,
    key_packages::*,
    messages::*,
    treesync::{node::Node, LeafNode, RatchetTree, RatchetTreeIn},
};
use ::rand::{rngs::OsRng, RngCore, TryRngCore};
use openmls_basic_credential::SignatureKeyPair;
use openmls_traits::{
    crypto::OpenMlsCrypto,
    types::{Ciphersuite, HpkeKeyPair, SignatureScheme},
    OpenMlsProvider as _,
};

use std::{collections::HashMap, sync::RwLock};
use tls_codec::*;

pub mod client;
pub mod errors;

use self::client::*;
use self::errors::*;

#[derive(Clone)]
/// The `Group` struct represents the "global" shared state of the group. Note,
/// that this state is only consistent if operations are conducted as per spec
/// and messages are distributed correctly to all clients via the
/// `distribute_to_members` function of `TestSetup`, which also updates the
/// `public_tree` field.
pub struct Group {
    pub group_id: GroupId,
    pub members: Vec<(usize, Vec<u8>)>,
    pub ciphersuite: Ciphersuite,
    pub group_config: MlsGroupJoinConfig,
    pub public_tree: RatchetTree,
    pub exporter_secret: Vec<u8>,
}

impl Group {
    /// Return the identity of a random member of the group.
    pub fn random_group_member(&self) -> (u32, Vec<u8>) {
        let index = (OsRng.unwrap_mut().next_u32() as usize) % self.members.len();
        let (i, identity) = self.members[index].clone();
        (i as u32, identity)
    }
    pub fn group_id(&self) -> &GroupId {
        &self.group_id
    }

    pub fn members(&self) -> impl Iterator<Item = (u32, Vec<u8>)> + '_ {
        self.members
            .clone()
            .into_iter()
            .map(|(index, id)| (index as u32, id))
    }
}

#[derive(Debug)]
pub enum ActionType {
    Commit,
    Proposal,
}

#[derive(Debug, PartialEq, Eq)]
pub enum CodecUse {
    SerializedMessages,
    StructMessages,
}

/// `MlsGroupTestSetup` is the main struct of the framework. It contains the
/// state of all clients. The `waiting_for_welcome` field acts as a temporary
/// store for `KeyPackage`s that are used to add new members to groups. Note,
/// that the `MlsGroupTestSetup` can only be initialized with a fixed number of
/// clients and that `create_clients` has to be called before it can be
/// otherwise used.
pub struct MlsGroupTestSetup<Provider: OpenMlsProvider> {
    // The clients identity is its position in the vector in be_bytes.
    pub clients: RwLock<HashMap<Vec<u8>, RwLock<Client<Provider>>>>,
    pub groups: RwLock<HashMap<GroupId, Group>>,
    // This maps key package hashes to client ids.
    pub waiting_for_welcome: RwLock<HashMap<Vec<u8>, Vec<u8>>>,
    pub default_mgp: MlsGroupCreateConfig,
    /// Flag to indicate if messages should be serialized and de-serialized as
    /// part of message distribution
    pub use_codec: CodecUse,
}

// Some notes regarding the layout of the `MlsGroupTestSetup` implementation
// below: The references to the credentials in the MlsGroups (in the
// clients) are essentially references to the keystore, which in turns means
// they are references to the top-level object, i.e. the setup.
//
// As a result, we can't have mutable references to the setup, because we have
// living (immutable) references to the keystore floating around. As a follow-up
// result, we need to populate the keystore _completely_ before distributing the
// references, as we can't mutably reference the keystore.
//
// Finally, this means we have to initialize the KeyStore before we create the
// MlsGroupTestSetup object and we can create the clients (which contain the
// references to the KeyStore) only after we do that. This has to happen in the
// context that the `MlsGroupTestSetup` lives in, because otherwise the
// references don't live long enough.

impl<Provider: OpenMlsProvider + Default> MlsGroupTestSetup<Provider> {
    /// Create a new `MlsGroupTestSetup` with the given default
    /// `MlsGroupCreateConfig` and the given number of clients. For lifetime
    /// reasons, `create_clients` has to be called in addition with the same
    /// number of clients.
    pub fn new(
        default_mgp: MlsGroupCreateConfig,
        number_of_clients: usize,
        use_codec: CodecUse,
    ) -> Self {
        let mut clients = HashMap::new();
        for i in 0..number_of_clients {
            let identity = i.to_be_bytes().to_vec();
            let provider = Provider::default();
            let mut credentials = HashMap::new();
            for ciphersuite in provider.crypto().supported_ciphersuites().iter() {
                let credential = BasicCredential::new(identity.clone());
                let signature_keys =
                    SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap();
                signature_keys.store(provider.storage()).unwrap();
                let signature_key = OpenMlsSignaturePublicKey::new(
                    signature_keys.public().into(),
                    signature_keys.signature_scheme(),
                )
                .unwrap();

                credentials.insert(
                    *ciphersuite,
                    CredentialWithKey {
                        credential: credential.into(),
                        signature_key: signature_key.into(),
                    },
                );
            }
            let client = Client {
                identity: identity.clone(),
                credentials,
                provider,
                groups: RwLock::new(HashMap::new()),
            };
            clients.insert(identity, RwLock::new(client));
        }
        let groups = RwLock::new(HashMap::new());
        let waiting_for_welcome = RwLock::new(HashMap::new());
        MlsGroupTestSetup {
            clients: RwLock::new(clients),
            groups,
            waiting_for_welcome,
            default_mgp,
            use_codec,
        }
    }

    /// Create a fresh `KeyPackage` for client `client` for use when adding it
    /// to a group. The `KeyPackageBundle` will be fetched automatically when
    /// delivering the `Welcome` via `deliver_welcome`. This function throws an
    /// error if the client does not support the given ciphersuite.
    pub fn get_fresh_key_package(
        &self,
        client: &Client<Provider>,
        ciphersuite: Ciphersuite,
    ) -> Result<KeyPackage, SetupError<Provider::StorageError>> {
        let key_package = client.get_fresh_key_package(ciphersuite)?;
        self.waiting_for_welcome
            .write()
            .expect("An unexpected error occurred.")
            .insert(
                key_package
                    .hash_ref(client.provider.crypto())?
                    .as_slice()
                    .to_vec(),
                client.identity.clone(),
            );
        Ok(key_package)
    }

    /// Convert an index in the tree into the corresponding identity.
    pub fn identity_by_index(&self, index: usize, group: &Group) -> Option<Vec<u8>> {
        let (_, id) = group
            .members
            .iter()
            .find(|(leaf_index, _)| index == *leaf_index)
            .expect("Couldn't find member at leaf index");
        let clients = self.clients.read().expect("An unexpected error occurred.");
        let client = clients
            .get(id)
            .expect("An unexpected error occurred.")
            .read()
            .expect("An unexpected error occurred.");
        client.identity(&group.group_id)
    }

    /// Convert an identity in the tree into the corresponding key package reference.
    pub fn identity_by_id(&self, id: &[u8], group: &Group) -> Option<Vec<u8>> {
        let (_, id) = group
            .members
            .iter()
            .find(|(_, leaf_id)| id == leaf_id)
            .expect("Couldn't find member at leaf index");
        let clients = self.clients.read().expect("An unexpected error occurred.");
        let client = clients
            .get(id)
            .expect("An unexpected error occurred.")
            .read()
            .expect("An unexpected error occurred.");
        client.identity(&group.group_id)
    }

    /// Deliver a Welcome message to the intended recipients. It uses the given
    /// group `group` to obtain the current public tree of the group. Note, that
    /// this tree only exists if `distribute_to_members` was previously used to
    /// distribute the commit adding the members to the group. This function
    /// will throw an error if no key package was previously created for the
    /// client by `get_fresh_key_package`.
    pub fn deliver_welcome(
        &self,
        welcome: Welcome,
        group: &Group,
    ) -> Result<(), SetupError<Provider::StorageError>> {
        // Serialize and de-serialize the Welcome if the bit was set.
        let welcome = match self.use_codec {
            CodecUse::SerializedMessages => {
                let serialized_welcome = welcome
                    .tls_serialize_detached()
                    .map_err(ClientError::TlsCodecError)?;
                Welcome::tls_deserialize(&mut serialized_welcome.as_slice())
                    .map_err(ClientError::TlsCodecError)?
            }
            CodecUse::StructMessages => welcome,
        };
        let clients = self.clients.read().expect("An unexpected error occurred.");
        for egs in welcome.secrets() {
            let client_id = self
                .waiting_for_welcome
                .write()
                .expect("An unexpected error occurred.")
                .remove(egs.new_member().as_slice())
                .ok_or(SetupError::NoFreshKeyPackage)?;
            let client = clients
                .get(&client_id)
                .expect("An unexpected error occurred.")
                .read()
                .expect("An unexpected error occurred.");
            client.join_group(
                group.group_config.clone(),
                welcome.clone(),
                Some(group.public_tree.clone().into()),
            )?;
        }
        Ok(())
    }

    /// Distribute a set of messages sent by the sender with identity
    /// `sender_id` to their intended recipients in group `Group`. This function
    /// also verifies that all members of that group agree on the public tree.
    pub fn distribute_to_members<AS: Fn(&Credential) -> bool>(
        &self,
        // We need the sender to know a group member that we know can not have
        // been removed from the group.
        sender_id: &[u8],
        group: &mut Group,
        message: &MlsMessageIn,
        authentication_service: &AS,
    ) -> Result<(), ClientError<Provider::StorageError>> {
        // Test serialization if mandated by config
        let message: ProtocolMessage = match self.use_codec {
            CodecUse::SerializedMessages => {
                let mls_message_out: MlsMessageOut = message.clone().into();
                let serialized_message = mls_message_out
                    .tls_serialize_detached()
                    .map_err(ClientError::TlsCodecError)?;

                MlsMessageIn::tls_deserialize(&mut serialized_message.as_slice())
                    .map_err(ClientError::TlsCodecError)?
            }
            CodecUse::StructMessages => message.clone(),
        }
        .into_protocol_message()
        .expect("Unexptected message type.");
        let clients = self.clients.read().expect("An unexpected error occurred.");
        // Distribute message to all members, except to the sender in the case of application messages
        let results: Result<Vec<_>, _> = group
            .members
            .iter()
            .filter_map(|(_index, member_id)| {
                if message.content_type() == ContentType::Application && member_id == sender_id {
                    None
                } else {
                    Some(member_id)
                }
            })
            .map(|member_id| {
                let member = clients
                    .get(member_id)
                    .expect("An unexpected error occurred.")
                    .read()
                    .expect("An unexpected error occurred.");
                member.receive_messages_for_group(&message, sender_id, &authentication_service)
            })
            .collect();

        // Check if we received an error
        results?;
        // Get the current tree and figure out who's still in the group.
        let sender = clients
            .get(sender_id)
            .expect("An unexpected error occurred.")
            .read()
            .expect("An unexpected error occurred.");
        let sender_groups = sender.groups.read().expect("An unexpected error occurred.");
        let sender_group = sender_groups
            .get(&group.group_id)
            .expect("An unexpected error occurred.");
        group.members = sender
            .get_members_of_group(&group.group_id)?
            .iter()
            .map(
                |Member {
                     index, credential, ..
                 }| { (index.usize(), credential.serialized_content().to_vec()) },
            )
            .collect();
        group.public_tree = sender_group.export_ratchet_tree();
        group.exporter_secret = sender_group
            .export_secret(sender.provider.crypto(), "test", &[], 32)
            .map_err(ClientError::ExportSecretError)?;
        Ok(())
    }

    /// Check if the public tree and the exporter secret with label "test" and
    /// length of the given group is the same for each group member. It also has
    /// each group member encrypt an application message and delivers all of
    /// these messages to all other members. This function panics if any of the
    /// above tests fail.
    pub fn check_group_states<AS: Fn(&Credential) -> bool>(
        &self,
        group: &mut Group,
        authentication_service: AS,
    ) {
        let clients = self.clients.read().expect("An unexpected error occurred.");

        let group_members = group.members.iter();

        let messages = group_members
            .filter_map(|(_, m_id)| {
                let m = clients
                    .get(m_id)
                    .expect("An unexpected error occurred.")
                    .read()
                    .expect("An unexpected error occurred.");
                let mut group_states = m.groups.write().expect("An unexpected error occurred.");
                // Some group members may not have received their welcome messages yet.
                if let Some(group_state) = group_states.get_mut(&group.group_id) {
                    assert_eq!(group_state.export_ratchet_tree(), group.public_tree);
                    assert_eq!(
                        group_state
                            .export_secret(m.provider.crypto(), "test", &[], 32)
                            .expect("An unexpected error occurred."),
                        group.exporter_secret
                    );
                    // Get the signature public key to read the signer from the
                    // key store.
                    let signature_pk = group_state.own_leaf().unwrap().signature_key();
                    let signer = SignatureKeyPair::read(
                        m.provider.storage(),
                        signature_pk.as_slice(),
                        group_state.ciphersuite().signature_algorithm(),
                    )
                    .unwrap();
                    let message = group_state
                        .create_message(&m.provider, &signer, "Hello World!".as_bytes())
                        .expect("Error composing message while checking group states.");
                    Some((m_id.to_vec(), message))
                } else {
                    None
                }
            })
            .collect::<Vec<(Vec<u8>, MlsMessageOut)>>();
        drop(clients);
        for (sender_id, message) in messages {
            self.distribute_to_members(&sender_id, group, &message.into(), &authentication_service)
                .expect("Error sending messages to clients while checking group states.");
        }
    }

    /// Get `number_of_members` new members for the given group `group` for use
    /// with the `add_members` function. If not enough clients are left that are
    /// not already members of this group, this function will return an error.
    /// TODO #310: Make this function ensure that the given members support the
    /// ciphersuite of the group.
    pub fn random_new_members_for_group(
        &self,
        group: &Group,
        number_of_members: usize,
    ) -> Result<Vec<Vec<u8>>, SetupError<Provider::StorageError>> {
        let clients = self.clients.read().expect("An unexpected error occurred.");
        if number_of_members + group.members.len() > clients.len() {
            return Err(SetupError::NotEnoughClients);
        }
        let mut new_member_ids: Vec<Vec<u8>> = Vec::new();

        for _ in 0..number_of_members {
            let is_in_new_members = |client_id| {
                new_member_ids
                    .iter()
                    .any(|new_member_id| client_id == new_member_id)
            };
            let is_in_group = |client_id| {
                group
                    .members
                    .iter()
                    .any(|(_, member_id)| client_id == member_id)
            };
            let new_member_id = clients
                .keys()
                .find(|&client_id| !is_in_group(client_id) && !is_in_new_members(client_id))
                .expect("An unexpected error occurred.");
            new_member_ids.push(new_member_id.clone());
        }
        Ok(new_member_ids)
    }

    /// Have a random client create a new group with ciphersuite `ciphersuite`
    /// and return the `GroupId`. Only works reliably if all clients support all
    /// ciphersuites, as it will throw an error if the randomly chosen client
    /// does not support the given ciphersuite. TODO #310: Fix to always work
    /// reliably, probably by introducing a mapping from ciphersuite to the set
    /// of client ids supporting it.
    pub fn create_group(
        &self,
        ciphersuite: Ciphersuite,
    ) -> Result<GroupId, SetupError<Provider::StorageError>> {
        // Pick a random group creator.
        let clients = self.clients.read().expect("An unexpected error occurred.");
        let group_creator_id = ((OsRng.unwrap_mut().next_u32() as usize) % clients.len())
            .to_be_bytes()
            .to_vec();
        let group_creator = clients
            .get(&group_creator_id)
            .expect("An unexpected error occurred.")
            .read()
            .expect("An unexpected error occurred.");
        let mut groups = self.groups.write().expect("An unexpected error occurred.");
        let group_id = group_creator.create_group(self.default_mgp.clone(), ciphersuite)?;
        let creator_groups = group_creator
            .groups
            .read()
            .expect("An unexpected error occurred.");
        let group = creator_groups
            .get(&group_id)
            .expect("An unexpected error occurred.");
        let public_tree = group.export_ratchet_tree();
        let exporter_secret =
            group.export_secret(group_creator.provider.crypto(), "test", &[], 32)?;
        let member_ids = vec![(0, group_creator_id)];
        let group = Group {
            group_id: group_id.clone(),
            members: member_ids,
            ciphersuite,
            group_config: self.default_mgp.join_config.clone(),
            public_tree,
            exporter_secret,
        };
        groups.insert(group_id.clone(), group);
        Ok(group_id)
    }

    /// Create a random group of size `group_size` and return the `GroupId`
    pub fn create_random_group<AS: Fn(&Credential) -> bool>(
        &self,
        target_group_size: usize,
        ciphersuite: Ciphersuite,
        authentication_service: AS,
    ) -> Result<GroupId, SetupError<Provider::StorageError>> {
        // Create the initial group.
        let group_id = self.create_group(ciphersuite)?;

        let mut groups = self.groups.write().expect("An unexpected error occurred.");
        let group = groups
            .get_mut(&group_id)
            .expect("An unexpected error occurred.");

        // Get new members to add to the group.
        let mut new_members = self.random_new_members_for_group(group, target_group_size - 1)?;

        // Add new members bit by bit.
        while !new_members.is_empty() {
            // Pick a random adder.
            let adder_id = group.random_group_member();
            // Add between 1 and 5 new members.
            let number_of_adds =
                ((OsRng.unwrap_mut().next_u32() as usize) % 5 % new_members.len()) + 1;
            let members_to_add = new_members.drain(0..number_of_adds).collect();
            self.add_clients(
                ActionType::Commit,
                group,
                &adder_id.1,
                members_to_add,
                &authentication_service,
            )?;
        }
        Ok(group_id)
    }

    /// Have the client with identity `client_id` either propose or commit
    /// (depending on `action_type`) a self update in group `group`. Will throw
    /// an error if the client is not actually a member of group `group`.
    pub fn self_update<AS: Fn(&Credential) -> bool>(
        &self,
        action_type: ActionType,
        group: &mut Group,
        client_id: &[u8],
        leaf_node_parameters: LeafNodeParameters,
        authentication_service: &AS,
    ) -> Result<(), SetupError<Provider::StorageError>> {
        let clients = self.clients.read().expect("An unexpected error occurred.");
        let client = clients
            .get(client_id)
            .ok_or(SetupError::UnknownClientId)?
            .read()
            .expect("An unexpected error occurred.");
        let (messages, welcome_option, _) =
            client.self_update(action_type, &group.group_id, leaf_node_parameters)?;
        self.distribute_to_members(
            &client.identity,
            group,
            &messages.into(),
            authentication_service,
        )?;
        if let Some(welcome) = welcome_option {
            self.deliver_welcome(welcome, group)?;
        }
        Ok(())
    }

    /// Has the `adder` either propose or commit (depending on the
    /// `action_type`) an add of the `addee` to the Group `group`. Returns an
    /// error if
    /// * the `adder` is not part of the group
    /// * the `addee` is already part of the group
    /// * the `addee` doesn't support the group's ciphersuite.
    pub fn add_clients<AS: Fn(&Credential) -> bool>(
        &self,
        action_type: ActionType,
        group: &mut Group,
        adder_id: &[u8],
        addees: Vec<Vec<u8>>,
        authentication_service: &AS,
    ) -> Result<(), SetupError<Provider::StorageError>> {
        let clients = self.clients.read().expect("An unexpected error occurred.");
        let adder = clients
            .get(adder_id)
            .ok_or(SetupError::UnknownClientId)?
            .read()
            .expect("An unexpected error occurred.");
        if group
            .members
            .iter()
            .any(|(_, id)| addees.iter().any(|client_id| client_id == id))
        {
            return Err(SetupError::ClientAlreadyInGroup);
        }
        let mut key_packages = Vec::new();
        for addee_id in &addees {
            let addee = clients
                .get(addee_id)
                .ok_or(SetupError::UnknownClientId)?
                .read()
                .expect("An unexpected error occurred.");
            let key_package = self.get_fresh_key_package(&addee, group.ciphersuite)?;
            key_packages.push(key_package);
        }
        let (messages, welcome_option, _) =
            adder.add_members(action_type, &group.group_id, &key_packages)?;
        for message in messages {
            self.distribute_to_members(adder_id, group, &message.into(), authentication_service)?;
        }
        if let Some(welcome) = welcome_option {
            self.deliver_welcome(welcome, group)?;
        }
        Ok(())
    }

    /// Has the `remover` propose or commit (depending on the `action_type`) the
    /// removal the `target_members` from the Group `group`. If the `remover` or
    /// one of the `target_members` is not part of the group, it returns an
    /// error.
    pub fn remove_clients<AS: Fn(&Credential) -> bool>(
        &self,
        action_type: ActionType,
        group: &mut Group,
        remover_id: &[u8],
        target_members: &[LeafNodeIndex],
        authentication_service: AS,
    ) -> Result<(), SetupError<Provider::StorageError>> {
        let clients = self.clients.read().expect("An unexpected error occurred.");
        let remover = clients
            .get(remover_id)
            .ok_or(SetupError::UnknownClientId)?
            .read()
            .expect("An unexpected error occurred.");
        let (messages, welcome_option, _) =
            remover.remove_members(action_type, &group.group_id, target_members)?;
        for message in messages {
            self.distribute_to_members(
                remover_id,
                group,
                &message.into(),
                &authentication_service,
            )?;
        }
        if let Some(welcome) = welcome_option {
            self.deliver_welcome(welcome, group)?;
        }
        Ok(())
    }

    /// This function picks a random member of group `group` and has them
    /// perform a random commit- or proposal action. TODO #133: This won't work
    /// yet due to the missing proposal validation.
    pub fn perform_random_operation<AS: Fn(&Credential) -> bool>(
        &self,
        group: &mut Group,
        authentication_service: &AS,
    ) -> Result<(), SetupError<Provider::StorageError>> {
        let mut rng = OsRng;
        let mut rng = rng.unwrap_mut();

        // Who's going to do it?
        let member_id = group.random_group_member();
        println!("Member performing the operation: {member_id:?}");

        // TODO: Do both things.
        let action_type = match (rng.next_u32() as usize) % 2 {
            0 => ActionType::Proposal,
            1 => ActionType::Commit,
            _ => return Err(SetupError::Unknown),
        };

        // TODO: Do multiple things.
        let operation_type = (rng.next_u32() as usize) % 3;
        match operation_type {
            0 => {
                println!("Performing a self-update with action type: {action_type:?}");
                self.self_update(
                    action_type,
                    group,
                    &member_id.1,
                    LeafNodeParameters::default(),
                    authentication_service,
                )?;
            }
            1 => {
                // If it's a single-member group, don't remove anyone.
                if group.members.len() > 1 {
                    // How many members?
                    let number_of_removals =
                        (((rng.next_u32() as usize) % group.members.len()) % 5) + 1;

                    let (own_index, _) = group
                        .members
                        .iter()
                        .find(|(_, identity)| identity == &member_id.1)
                        .expect("An unexpected error occurred.")
                        .clone();
                    println!("Index of the member performing the {action_type:?}: {own_index:?}");

                    let mut target_member_leaf_indices = Vec::new();
                    let mut target_member_identities = Vec::new();
                    let clients = self.clients.read().expect("An unexpected error occurred.");
                    // Get the client references, as opposed to just the member indices.
                    println!("Removing members:");
                    for _ in 0..number_of_removals {
                        // Get a random index.
                        let mut member_list_index = (rng.next_u32() as usize) % group.members.len();
                        // Re-sample until the index is not our own index and
                        // not one that is not already being removed.
                        let (mut leaf_index, mut identity) =
                            group.members[member_list_index].clone();
                        while leaf_index == own_index
                            || target_member_identities.contains(&identity)
                        {
                            member_list_index = (rng.next_u32() as usize) % group.members.len();
                            let (new_leaf_index, new_identity) =
                                group.members[member_list_index].clone();
                            leaf_index = new_leaf_index;
                            identity = new_identity;
                        }
                        let client = clients
                            .get(&identity)
                            .expect("An unexpected error occurred.")
                            .read()
                            .expect("An unexpected error occurred.");
                        let client_group =
                            client.groups.read().expect("An unexpected error occurred.");
                        let client_group = client_group
                            .get(&group.group_id)
                            .expect("An unexpected error occurred.");
                        target_member_leaf_indices.push(client_group.own_leaf_index());
                        target_member_identities.push(identity);
                    }
                    self.remove_clients(
                        action_type,
                        group,
                        &member_id.1,
                        &target_member_leaf_indices,
                        authentication_service,
                    )?
                };
            }
            2 => {
                // First, figure out if there are clients left to add.
                let clients_left = self
                    .clients
                    .read()
                    .expect("An unexpected error occurred.")
                    .len()
                    - group.members.len();
                if clients_left > 0 {
                    let number_of_adds = (((rng.next_u32() as usize) % clients_left) % 5) + 1;
                    let new_member_ids = self
                        .random_new_members_for_group(group, number_of_adds)
                        .expect("An unexpected error occurred.");
                    println!("{action_type:?}: Adding new clients: {new_member_ids:?}");
                    // Have the adder add them to the group.
                    self.add_clients(
                        action_type,
                        group,
                        &member_id.1,
                        new_member_ids,
                        authentication_service,
                    )?;
                }
            }
            _ => return Err(SetupError::Unknown),
        };
        Ok(())
    }
}

// This function signature is used in the callback for credential validation.
// This particular function just accepts any credential. If you want to validate
// credentials in your test, don't use this.
pub fn noop_authentication_service(_cred: &Credential) -> bool {
    true
}