1use openmls_traits::{
2 crypto::OpenMlsCrypto,
3 types::{Ciphersuite, CryptoError},
4 OpenMlsProvider,
5};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use tls_codec::{
9 DeserializeBytes, SecretVLByteVec, Serialize as _, Size as _, TlsDeserializeBytes,
10 TlsSerialize, TlsSize, VLByteSlice, VLByteVec,
11};
12
13use crate::{
14 binary_tree::{array_representation::TreeSize, LeafNodeIndex},
15 ciphersuite::{hash_ref::KeyPackageRef, Secret},
16 group::{GroupEpoch, GroupId},
17 key_packages::InitKey,
18 messages::PathSecret,
19 treesync::node::encryption_keys::EncryptionKeyPair,
20};
21
22pub const VC_COMPONENT_ID: u16 = 0x667A;
28
29const ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
36const PATH_GENERATION_LABEL: &str = "Path Generation";
37const INIT_KEY_LABEL: &str = "Init Key";
38const KEY_PACKAGE_SEED_LABEL: &str = "vc key package seed";
44const TARGET_OPERATION_LABEL: &str = "vc target operation";
47const GROUP_CREATION_LABEL: &str = "Group Creation";
50
51const DERIVATION_INFO_KEY_LABEL: &str = "key";
54const DERIVATION_INFO_NONCE_LABEL: &str = "nonce";
57
58const EPOCH_ID_LABEL: &str = "Epoch ID";
59const EPOCH_ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
60const EPOCH_BASE_SECRET_LABEL: &str = "Base Secret";
61const REUSE_GUARD_LABEL: &str = "Reuse Guard";
63const GENERATION_ID_LABEL: &str = "Generation ID Secret";
65const GENERATION_ID_EXPAND_LABEL: &str = "generation id";
69const REUSE_GUARD_PRP_KEY_LABEL: &str = "reuse guard";
72const PRP_KEY_LEN: usize = 16;
74
75#[derive(Error, Debug, PartialEq, Clone)]
77pub enum VirtualClientsError {
78 #[error("Failed to deserialize derivation info.")]
80 DerivationInfoMalformed,
81 #[error("Failed to decrypt derivation info.")]
84 DerivationInfoDecryptionFailed,
85 #[error("No virtual-clients operation secret tree for this epoch.")]
88 MissingOperationTree,
89 #[error("No virtual-clients emulation-epoch state for this epoch.")]
92 MissingEmulationEpochState,
93 #[error("Virtual-clients storage error")]
96 StorageError,
97 #[error("Leaf encryption key from path does not match the derived key.")]
100 EncryptionKeyMismatch,
101 #[error("Cryptographic operation failed.")]
103 CryptoError(#[from] CryptoError),
104 #[error(
106 "Hash function produced output of length {actual_length}, expected {expected_length}."
107 )]
108 HashOutputLengthMismatch {
109 actual_length: usize,
111 expected_length: usize,
113 },
114 #[error("TLS codec error: {0}")]
118 Tls(#[from] tls_codec::Error),
119 #[error("Leaf does not declare AppDataDictionary support in its capabilities.")]
122 AppDataDictionaryNotSupported,
123 #[error("Leaf's AppComponents entry does not list the virtual-clients component id.")]
127 VcComponentNotListed,
128 #[error("Leaf index is outside the operation secret tree.")]
130 IndexOutOfBounds,
131 #[error("The operation secret for this generation was already consumed.")]
134 OperationGenerationConsumed,
135 #[error("The requested operation generation is too far beyond the ratchet head.")]
139 OperationGenerationTooDistant,
140 #[error("Operation ratchet generation has reached `u32::MAX`.")]
142 OperationRatchetTooLong,
143 #[error("An unrecoverable error has occurred due to a bug in the implementation.")]
146 LibraryError,
147 #[error("KeyPackageUpload contains a duplicate key_package_index: {0}.")]
150 DuplicateKeyPackageIndex(u32),
151 #[error("KeyPackageUpload contains a duplicate KeyPackageRef.")]
154 DuplicateKeyPackageRef,
155}
156
157#[derive(Debug, Serialize, Deserialize)]
163pub(crate) struct EmulatorEpochSecret(Secret);
164
165impl EmulatorEpochSecret {
166 pub(crate) fn new(bytes: &[u8]) -> Self {
170 Self(Secret::from_slice(bytes))
171 }
172
173 pub(crate) fn derive_epoch_id(
174 &self,
175 crypto: &impl OpenMlsCrypto,
176 ciphersuite: Ciphersuite,
177 ) -> Result<EpochId, VirtualClientsError> {
178 let secret = self.0.derive_secret(crypto, ciphersuite, EPOCH_ID_LABEL)?;
179 Ok(EpochId(secret.as_slice().to_vec().into()))
180 }
181
182 pub(crate) fn derive_epoch_encryption_key(
186 &self,
187 crypto: &impl OpenMlsCrypto,
188 ciphersuite: Ciphersuite,
189 ) -> Result<EpochEncryptionKey, VirtualClientsError> {
190 let secret = self
191 .0
192 .derive_secret(crypto, ciphersuite, EPOCH_ENCRYPTION_KEY_LABEL)?;
193 Ok(EpochEncryptionKey(secret))
194 }
195
196 pub(crate) fn derive_epoch_base_secret(
197 &self,
198 crypto: &impl OpenMlsCrypto,
199 ciphersuite: Ciphersuite,
200 ) -> Result<Secret, VirtualClientsError> {
201 Ok(self
202 .0
203 .derive_secret(crypto, ciphersuite, EPOCH_BASE_SECRET_LABEL)?)
204 }
205
206 pub(crate) fn derive_reuse_guard_secret(
208 &self,
209 crypto: &impl OpenMlsCrypto,
210 ciphersuite: Ciphersuite,
211 ) -> Result<ReuseGuardSecret, VirtualClientsError> {
212 let secret = self
213 .0
214 .derive_secret(crypto, ciphersuite, REUSE_GUARD_LABEL)?;
215 Ok(ReuseGuardSecret(secret))
216 }
217
218 pub(crate) fn derive_generation_id_secret(
220 &self,
221 crypto: &impl OpenMlsCrypto,
222 ciphersuite: Ciphersuite,
223 ) -> Result<GenerationIdSecret, VirtualClientsError> {
224 let secret = self
225 .0
226 .derive_secret(crypto, ciphersuite, GENERATION_ID_LABEL)?;
227 Ok(GenerationIdSecret(secret))
228 }
229}
230
231#[derive(Debug, Serialize, Deserialize)]
235pub(crate) struct ReuseGuardSecret(Secret);
236
237impl ReuseGuardSecret {
238 #[cfg(test)]
240 pub(crate) fn from_secret_for_tests(secret: Secret) -> Self {
241 Self(secret)
242 }
243
244 pub(crate) fn derive_prp_key(
254 &self,
255 crypto: &impl OpenMlsCrypto,
256 ciphersuite: Ciphersuite,
257 key_schedule_nonce: &[u8],
258 ) -> Result<[u8; PRP_KEY_LEN], VirtualClientsError> {
259 let key = self.0.kdf_expand_label(
260 crypto,
261 ciphersuite,
262 REUSE_GUARD_PRP_KEY_LABEL,
263 key_schedule_nonce,
264 PRP_KEY_LEN,
265 )?;
266 key.as_slice()
267 .try_into()
268 .map_err(|_| VirtualClientsError::HashOutputLengthMismatch {
269 actual_length: key.as_slice().len(),
270 expected_length: PRP_KEY_LEN,
271 })
272 }
273}
274
275#[derive(Debug, Serialize, Deserialize)]
280pub(crate) struct GenerationIdSecret(Secret);
281
282impl GenerationIdSecret {
283 fn derive_generation_id(
294 &self,
295 crypto: &impl OpenMlsCrypto,
296 ciphersuite: Ciphersuite,
297 context: &PrivateMessageContext<'_>,
298 ) -> Result<GenerationId, VirtualClientsError> {
299 let context_bytes = context.tls_serialize_detached()?;
300 let generation_id = self.0.kdf_expand_label(
301 crypto,
302 ciphersuite,
303 GENERATION_ID_EXPAND_LABEL,
304 &context_bytes,
305 ciphersuite.hash_length(),
306 )?;
307 Ok(GenerationId(generation_id.as_slice().to_vec().into()))
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq, TlsSize, TlsSerialize)]
328#[repr(u8)]
329pub enum RatchetType {
330 Application = 1,
332 Handshake = 2,
334}
335
336#[derive(Debug, TlsSize, TlsSerialize)]
353pub(crate) struct PrivateMessageContext<'a> {
354 group_id: VLByteSlice<'a>,
355 epoch: u64,
356 generation: u32,
357 ratchet_type: RatchetType,
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
372pub struct GenerationId(VLByteVec);
373
374impl GenerationId {
375 pub fn as_slice(&self) -> &[u8] {
377 self.0.as_slice()
378 }
379}
380
381#[derive(Debug, TlsSize, TlsSerialize, TlsDeserializeBytes)]
397pub(crate) struct DerivationInfo {
398 epoch_id: EpochId,
399 ciphertext: VLByteVec,
400}
401
402impl DerivationInfo {
403 pub(crate) fn encrypt(
408 crypto: &impl OpenMlsCrypto,
409 ciphersuite: Ciphersuite,
410 key: &EpochEncryptionKey,
411 epoch_id: EpochId,
412 leaf_encryption_key: &[u8],
413 tbe: &DerivationInfoTbe,
414 ) -> Result<Self, VirtualClientsError> {
415 let (aead_key, aead_nonce) =
416 key.derive_key_nonce(crypto, ciphersuite, leaf_encryption_key)?;
417 let payload = tbe.tls_serialize_detached()?;
418 let ciphertext = crypto.aead_encrypt(
419 ciphersuite.aead_algorithm(),
420 aead_key.as_slice(),
421 payload.as_slice(),
422 aead_nonce.as_slice(),
423 epoch_id.0.as_slice(),
424 )?;
425 Ok(Self {
426 epoch_id,
427 ciphertext: ciphertext.into(),
428 })
429 }
430
431 pub(crate) fn epoch_id(&self) -> &EpochId {
432 &self.epoch_id
433 }
434
435 pub(crate) fn decrypt(
439 &self,
440 crypto: &impl OpenMlsCrypto,
441 ciphersuite: Ciphersuite,
442 key: &EpochEncryptionKey,
443 leaf_encryption_key: &[u8],
444 operation_type: VirtualClientOperationType,
445 ) -> Result<DerivationInfoTbe, VirtualClientsError> {
446 let (aead_key, aead_nonce) =
447 key.derive_key_nonce(crypto, ciphersuite, leaf_encryption_key)?;
448 let plaintext = crypto
449 .aead_decrypt(
450 ciphersuite.aead_algorithm(),
451 aead_key.as_slice(),
452 self.ciphertext.as_slice(),
453 aead_nonce.as_slice(),
454 self.epoch_id.0.as_slice(),
455 )
456 .map_err(|e| {
457 log::error!("vc: aead decrypt derivation info failed: {e:?}");
458 VirtualClientsError::DerivationInfoDecryptionFailed
459 })?;
460 DerivationInfoTbe::deserialize_for_operation(&plaintext, operation_type)
461 }
462}
463
464#[derive(
471 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TlsSize, TlsSerialize, TlsDeserializeBytes,
472)]
473pub struct EpochId(VLByteVec);
474
475impl EpochId {
476 pub fn new(bytes: Vec<u8>) -> Self {
478 Self(bytes.into())
479 }
480
481 pub fn as_bytes(&self) -> &[u8] {
483 self.0.as_slice()
484 }
485}
486
487#[derive(Debug, TlsSize, TlsSerialize, TlsDeserializeBytes)]
507pub struct KeyPackageInfo {
508 pub key_package_ref: KeyPackageRef,
510 pub cipher_suite: Ciphersuite,
512 pub key_package_index: u32,
514}
515
516#[derive(Debug, TlsSize, TlsSerialize, TlsDeserializeBytes)]
539pub struct KeyPackageUpload {
540 pub epoch_id: EpochId,
542 pub leaf_index: LeafNodeIndex,
544 pub generation: u32,
546 pub key_package_info: Vec<KeyPackageInfo>,
548}
549
550#[derive(Debug, Serialize, Deserialize)]
563pub struct RetainedKeyPackageMaterial {
564 pub epoch_id: EpochId,
566 pub leaf_index: LeafNodeIndex,
569 pub generation: u32,
571 pub key_package_ciphersuite: Ciphersuite,
573 pub key_package_index: u32,
575 pub key_package_seed_secret: KeyPackageSeedSecret,
578}
579
580fn validate_key_package_infos(infos: &[KeyPackageInfo]) -> Result<(), VirtualClientsError> {
590 let mut seen_indices = std::collections::BTreeSet::new();
591 let mut seen_refs = std::collections::BTreeSet::new();
592 for info in infos {
593 if !seen_indices.insert(info.key_package_index) {
594 return Err(VirtualClientsError::DuplicateKeyPackageIndex(
595 info.key_package_index,
596 ));
597 }
598 if !seen_refs.insert(&info.key_package_ref) {
599 return Err(VirtualClientsError::DuplicateKeyPackageRef);
600 }
601 }
602 Ok(())
603}
604
605pub fn assemble_vc_key_package_upload<Storage: crate::storage::StorageProvider>(
619 storage: &Storage,
620 epoch_id: EpochId,
621 generation: u32,
622 key_package_info: Vec<KeyPackageInfo>,
623) -> Result<KeyPackageUpload, VirtualClientsError> {
624 validate_key_package_infos(&key_package_info)?;
625 let state: EmulationEpochState = storage
626 .vc_emulation_epoch_state(&epoch_id)
627 .map_err(|e| {
628 log::error!("vc: load emulation epoch state in assemble upload failed: {e:?}");
629 VirtualClientsError::StorageError
630 })?
631 .ok_or(VirtualClientsError::MissingEmulationEpochState)?;
632 Ok(KeyPackageUpload {
633 epoch_id,
634 leaf_index: state.leaf_index,
635 generation,
636 key_package_info,
637 })
638}
639
640pub fn process_vc_key_package_upload<Provider: OpenMlsProvider>(
655 provider: &Provider,
656 upload: &KeyPackageUpload,
657) -> Result<(), VirtualClientsError> {
658 use crate::components::vc_operation_tree::OperationSecretTree;
659 use openmls_traits::storage::StorageProvider as _;
660
661 validate_key_package_infos(&upload.key_package_info)?;
662
663 let storage = provider.storage();
664 let crypto = provider.crypto();
665
666 let state: EmulationEpochState = storage
667 .vc_emulation_epoch_state(&upload.epoch_id)
668 .map_err(|e| {
669 log::error!("vc: load emulation epoch state in process upload failed: {e:?}");
670 VirtualClientsError::StorageError
671 })?
672 .ok_or(VirtualClientsError::MissingEmulationEpochState)?;
673 let mut operation_tree: OperationSecretTree = storage
674 .vc_operation_tree(&upload.epoch_id)
675 .map_err(|e| {
676 log::error!("vc: load operation tree in process upload failed: {e:?}");
677 VirtualClientsError::StorageError
678 })?
679 .ok_or(VirtualClientsError::MissingOperationTree)?;
680 let emulation_ciphersuite = state.emulation_ciphersuite;
681
682 let operation_secret = operation_tree.derive_operation_secret(
684 crypto,
685 emulation_ciphersuite,
686 &upload.epoch_id,
687 upload.leaf_index,
688 VirtualClientOperationType::KeyPackage,
689 upload.generation,
690 b"",
691 )?;
692
693 let mut materials = Vec::with_capacity(upload.key_package_info.len());
694 for info in &upload.key_package_info {
695 let key_package_seed_secret = operation_secret.derive_key_package_seed_secret(
696 crypto,
697 info.cipher_suite,
698 info.key_package_index,
699 )?;
700 let material = RetainedKeyPackageMaterial {
701 epoch_id: upload.epoch_id.clone(),
702 leaf_index: upload.leaf_index,
703 generation: upload.generation,
704 key_package_ciphersuite: info.cipher_suite,
705 key_package_index: info.key_package_index,
706 key_package_seed_secret,
707 };
708 materials.push((info.key_package_ref.clone(), material));
709 }
710
711 storage
712 .write_retained_key_package_material_batch(&upload.epoch_id, &operation_tree, &materials)
713 .map_err(|e| {
714 log::error!("vc: persist batch key package material in process upload failed: {e:?}");
715 VirtualClientsError::StorageError
716 })?;
717 Ok(())
718}
719
720#[derive(Debug)]
731pub(crate) struct VcWelcomeMaterial {
732 pub(crate) key_package_ref: KeyPackageRef,
734 pub(crate) epoch_id: EpochId,
736 pub(crate) leaf_index: LeafNodeIndex,
739 pub(crate) generation: u32,
741 pub(crate) key_package_index: u32,
743 pub(crate) init_private_key: openmls_traits::types::HpkePrivateKey,
746 pub(crate) init_key: InitKey,
748 pub(crate) encryption_keypair: EncryptionKeyPair,
751}
752
753#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
766pub(crate) struct RegisteredVcEmulationEpoch {
767 pub(crate) group_epoch: crate::group::GroupEpoch,
769 pub(crate) epoch_id: EpochId,
771}
772
773#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
785pub struct VcEmulationBindings {
786 bindings: std::collections::VecDeque<(crate::group::GroupEpoch, EpochId)>,
788}
789
790impl VcEmulationBindings {
791 pub fn get(&self, epoch: crate::group::GroupEpoch) -> Option<&EpochId> {
793 for (bound_epoch, epoch_id) in &self.bindings {
794 if *bound_epoch == epoch {
795 return Some(epoch_id);
796 }
797 }
798 None
799 }
800
801 pub(crate) fn insert(
804 &mut self,
805 epoch: crate::group::GroupEpoch,
806 epoch_id: EpochId,
807 max_entries: usize,
808 ) {
809 self.bindings
810 .retain(|(bound_epoch, _)| *bound_epoch != epoch);
811 self.bindings.push_back((epoch, epoch_id));
812 while self.bindings.len() > max_entries {
813 self.bindings.pop_front();
814 }
815 }
816}
817
818#[derive(Debug, Serialize, Deserialize)]
837pub(crate) struct EpochEncryptionKey(Secret);
838
839impl EpochEncryptionKey {
840 fn derive_key_nonce(
844 &self,
845 crypto: &impl OpenMlsCrypto,
846 ciphersuite: Ciphersuite,
847 leaf_encryption_key: &[u8],
848 ) -> Result<(Secret, Secret), VirtualClientsError> {
849 let key = self.0.kdf_expand_label(
850 crypto,
851 ciphersuite,
852 DERIVATION_INFO_KEY_LABEL,
853 leaf_encryption_key,
854 ciphersuite.aead_key_length(),
855 )?;
856 let nonce = self.0.kdf_expand_label(
857 crypto,
858 ciphersuite,
859 DERIVATION_INFO_NONCE_LABEL,
860 leaf_encryption_key,
861 ciphersuite.aead_nonce_length(),
862 )?;
863 Ok((key, nonce))
864 }
865}
866
867#[derive(Debug, Serialize, Deserialize)]
876pub struct EmulationEpochState {
877 pub(crate) leaf_index: LeafNodeIndex,
881 pub(crate) epoch_encryption_key: EpochEncryptionKey,
882 pub(crate) reuse_guard_secret: ReuseGuardSecret,
883 pub(crate) generation_id_secret: GenerationIdSecret,
886 pub(crate) emulation_group_size: TreeSize,
888 pub(crate) emulation_ciphersuite: Ciphersuite,
891}
892
893impl EmulationEpochState {
894 pub(crate) fn new(
895 leaf_index: LeafNodeIndex,
896 epoch_encryption_key: EpochEncryptionKey,
897 reuse_guard_secret: ReuseGuardSecret,
898 generation_id_secret: GenerationIdSecret,
899 emulation_group_size: TreeSize,
900 emulation_ciphersuite: Ciphersuite,
901 ) -> Self {
902 Self {
903 leaf_index,
904 epoch_encryption_key,
905 reuse_guard_secret,
906 generation_id_secret,
907 emulation_group_size,
908 emulation_ciphersuite,
909 }
910 }
911
912 pub(crate) fn into_parts(self) -> (LeafNodeIndex, EpochEncryptionKey, Ciphersuite) {
915 (
916 self.leaf_index,
917 self.epoch_encryption_key,
918 self.emulation_ciphersuite,
919 )
920 }
921
922 pub(crate) fn derive_generation_id(
928 &self,
929 crypto: &impl OpenMlsCrypto,
930 group_id: &GroupId,
931 epoch: GroupEpoch,
932 generation: u32,
933 ratchet_type: RatchetType,
934 ) -> Result<GenerationId, VirtualClientsError> {
935 let context = PrivateMessageContext {
936 group_id: VLByteSlice(group_id.as_slice()),
937 epoch: epoch.as_u64(),
938 generation,
939 ratchet_type,
940 };
941 self.generation_id_secret
942 .derive_generation_id(crypto, self.emulation_ciphersuite, &context)
943 }
944
945 pub(crate) fn reuse_guard_inputs(&self) -> crate::framing::EmulatorReuseGuardCtx<'_> {
948 crate::framing::EmulatorReuseGuardCtx {
949 reuse_guard_secret: &self.reuse_guard_secret,
950 emulation_ciphersuite: self.emulation_ciphersuite,
951 emulation_group_size: self.emulation_group_size,
952 emulation_leaf_index: self.leaf_index,
953 }
954 }
955}
956
957#[derive(Debug, Serialize, Deserialize)]
965pub struct OperationSecret(Secret);
966
967impl From<Secret> for OperationSecret {
968 fn from(secret: Secret) -> Self {
969 Self(secret)
970 }
971}
972
973fn import_secret(
977 crypto: &impl OpenMlsCrypto,
978 target_ciphersuite: Ciphersuite,
979 source_secret: &Secret,
980 label: &str,
981 context: &[u8],
982) -> Result<Secret, CryptoError> {
983 let salt = Secret::from_slice(&[]);
984 let target_prk = salt.hkdf_extract(crypto, target_ciphersuite, source_secret)?;
985 target_prk.kdf_expand_label(
986 crypto,
987 target_ciphersuite,
988 label,
989 context,
990 target_ciphersuite.hash_length(),
991 )
992}
993
994impl OperationSecret {
995 #[cfg(test)]
997 pub(crate) fn as_slice(&self) -> &[u8] {
998 self.0.as_slice()
999 }
1000
1001 pub(crate) fn derive_target_operation_secret(
1017 &self,
1018 crypto: &impl OpenMlsCrypto,
1019 target_ciphersuite: Ciphersuite,
1020 group_id: &GroupId,
1021 ) -> Result<TargetOperationSecret, VirtualClientsError> {
1022 let context = TargetOperationContext {
1023 cipher_suite: target_ciphersuite,
1024 group_id: VLByteSlice(group_id.as_slice()),
1025 }
1026 .tls_serialize_detached()?;
1027 let secret = import_secret(
1028 crypto,
1029 target_ciphersuite,
1030 &self.0,
1031 TARGET_OPERATION_LABEL,
1032 &context,
1033 )?;
1034 Ok(TargetOperationSecret(secret))
1035 }
1036
1037 pub(crate) fn derive_key_package_seed_secret(
1051 &self,
1052 crypto: &impl OpenMlsCrypto,
1053 target_ciphersuite: Ciphersuite,
1054 key_package_index: u32,
1055 ) -> Result<KeyPackageSeedSecret, VirtualClientsError> {
1056 let context = KeyPackageSeedContext {
1057 cipher_suite: target_ciphersuite,
1058 key_package_index,
1059 }
1060 .tls_serialize_detached()?;
1061 let seed = import_secret(
1062 crypto,
1063 target_ciphersuite,
1064 &self.0,
1065 KEY_PACKAGE_SEED_LABEL,
1066 &context,
1067 )?;
1068 Ok(KeyPackageSeedSecret(seed))
1069 }
1070}
1071
1072#[derive(Debug, TlsSize, TlsSerialize)]
1085struct KeyPackageSeedContext {
1086 cipher_suite: Ciphersuite,
1087 key_package_index: u32,
1088}
1089
1090#[derive(Debug, Serialize, Deserialize)]
1097pub struct KeyPackageSeedSecret(Secret);
1098
1099impl KeyPackageSeedSecret {
1100 pub(crate) fn derive_init_key_secret(
1101 &self,
1102 crypto: &impl OpenMlsCrypto,
1103 ciphersuite: Ciphersuite,
1104 ) -> Result<InitKeySecret, VirtualClientsError> {
1105 let init_key_secret = self.0.derive_secret(crypto, ciphersuite, INIT_KEY_LABEL)?;
1106 Ok(InitKeySecret(init_key_secret))
1107 }
1108
1109 pub(crate) fn derive_encryption_key_secret(
1110 &self,
1111 crypto: &impl OpenMlsCrypto,
1112 ciphersuite: Ciphersuite,
1113 ) -> Result<EncryptionKeySecret, VirtualClientsError> {
1114 let encryption_key_secret =
1115 self.0
1116 .derive_secret(crypto, ciphersuite, ENCRYPTION_KEY_LABEL)?;
1117 Ok(EncryptionKeySecret(encryption_key_secret))
1118 }
1119
1120 pub(crate) fn derive_group_creation_secret(
1131 &self,
1132 crypto: &impl OpenMlsCrypto,
1133 ciphersuite: Ciphersuite,
1134 ) -> Result<Secret, VirtualClientsError> {
1135 Ok(self
1136 .0
1137 .derive_secret(crypto, ciphersuite, GROUP_CREATION_LABEL)?)
1138 }
1139}
1140
1141pub(crate) struct EncryptionKeySecret(Secret);
1142
1143impl EncryptionKeySecret {
1144 pub(crate) fn generate_encryption_key_pair(
1145 &self,
1146 crypto: &impl OpenMlsCrypto,
1147 ciphersuite: Ciphersuite,
1148 ) -> Result<EncryptionKeyPair, VirtualClientsError> {
1149 let hpke_config = ciphersuite.hpke_config();
1150 let key_pair = crypto.derive_hpke_keypair(hpke_config, self.0.as_slice())?;
1151 Ok(EncryptionKeyPair::from(key_pair))
1152 }
1153}
1154
1155pub(crate) struct InitKeySecret(Secret);
1156
1157impl InitKeySecret {
1158 pub(crate) fn generate_init_key_pair(
1159 &self,
1160 crypto: &impl OpenMlsCrypto,
1161 ciphersuite: Ciphersuite,
1162 ) -> Result<openmls_traits::types::HpkeKeyPair, VirtualClientsError> {
1163 let hpke_config = ciphersuite.hpke_config();
1164 let key_pair = crypto.derive_hpke_keypair(hpke_config, self.0.as_slice())?;
1165 Ok(key_pair)
1166 }
1167}
1168
1169pub(crate) struct PathGenerationSecret(Secret);
1170
1171impl From<PathGenerationSecret> for PathSecret {
1172 fn from(value: PathGenerationSecret) -> Self {
1173 value.0.into()
1174 }
1175}
1176
1177#[derive(Debug, Clone, Copy, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
1193#[repr(u8)]
1194pub enum VirtualClientOperationType {
1195 KeyPackage = 1,
1197 LeafNode = 2,
1200 Application = 3,
1202}
1203
1204#[derive(Clone, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
1217pub(crate) struct ExternalInitSecret(SecretVLByteVec);
1218
1219impl std::fmt::Debug for ExternalInitSecret {
1220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1221 f.debug_struct("ExternalInitSecret")
1222 .field("init_secret", &"<redacted>")
1223 .finish()
1224 }
1225}
1226
1227impl ExternalInitSecret {
1228 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1229 Self(bytes.to_vec().into())
1230 }
1231
1232 pub(crate) fn as_slice(&self) -> &[u8] {
1233 self.0.as_slice()
1234 }
1235}
1236
1237#[derive(Debug, TlsSize, TlsSerialize)]
1244struct TargetOperationContext<'a> {
1245 cipher_suite: Ciphersuite,
1246 group_id: VLByteSlice<'a>,
1247}
1248
1249#[derive(Debug)]
1253pub(crate) struct TargetOperationSecret(Secret);
1254
1255impl TargetOperationSecret {
1256 pub(crate) fn derive_encryption_key_secret(
1257 &self,
1258 crypto: &impl OpenMlsCrypto,
1259 ciphersuite: Ciphersuite,
1260 ) -> Result<EncryptionKeySecret, VirtualClientsError> {
1261 let encryption_key_secret =
1262 self.0
1263 .derive_secret(crypto, ciphersuite, ENCRYPTION_KEY_LABEL)?;
1264 Ok(EncryptionKeySecret(encryption_key_secret))
1265 }
1266
1267 pub(crate) fn derive_path_generation_secret(
1268 &self,
1269 crypto: &impl OpenMlsCrypto,
1270 ciphersuite: Ciphersuite,
1271 ) -> Result<PathGenerationSecret, VirtualClientsError> {
1272 let path_generation_secret =
1273 self.0
1274 .derive_secret(crypto, ciphersuite, PATH_GENERATION_LABEL)?;
1275 Ok(PathGenerationSecret(path_generation_secret))
1276 }
1277}
1278
1279#[derive(Debug)]
1288pub(crate) struct VcCommitMaterial {
1289 pub(crate) epoch_id: EpochId,
1291 pub(crate) operation_secret: OperationSecret,
1293 pub(crate) external_init_secret: Option<ExternalInitSecret>,
1295}
1296
1297#[derive(Debug, PartialEq, Eq)]
1321pub(crate) enum DerivationInfoTbe {
1322 LeafNode {
1327 leaf_index: LeafNodeIndex,
1328 generation: u32,
1329 external_init_secret: Option<ExternalInitSecret>,
1331 },
1332 KeyPackage {
1334 leaf_index: LeafNodeIndex,
1335 generation: u32,
1336 key_package_index: u32,
1337 },
1338}
1339
1340impl DerivationInfoTbe {
1341 pub(crate) fn leaf_index(&self) -> LeafNodeIndex {
1343 match self {
1344 Self::LeafNode { leaf_index, .. } | Self::KeyPackage { leaf_index, .. } => *leaf_index,
1345 }
1346 }
1347
1348 pub(crate) fn generation(&self) -> u32 {
1350 match self {
1351 Self::LeafNode { generation, .. } | Self::KeyPackage { generation, .. } => *generation,
1352 }
1353 }
1354
1355 pub(crate) fn external_init_secret(&self) -> Option<&ExternalInitSecret> {
1358 match self {
1359 Self::LeafNode {
1360 external_init_secret,
1361 ..
1362 } => external_init_secret.as_ref(),
1363 Self::KeyPackage { .. } => None,
1364 }
1365 }
1366
1367 fn tls_serialize_detached(&self) -> Result<Vec<u8>, tls_codec::Error> {
1371 match self {
1372 Self::LeafNode {
1373 leaf_index,
1374 generation,
1375 external_init_secret,
1376 } => {
1377 let mut out = Vec::with_capacity(
1378 leaf_index.tls_serialized_len()
1379 + generation.tls_serialized_len()
1380 + external_init_secret.tls_serialized_len(),
1381 );
1382 leaf_index.tls_serialize(&mut out)?;
1383 generation.tls_serialize(&mut out)?;
1384 external_init_secret.tls_serialize(&mut out)?;
1385 Ok(out)
1386 }
1387 Self::KeyPackage {
1388 leaf_index,
1389 generation,
1390 key_package_index,
1391 } => {
1392 let mut out = Vec::with_capacity(
1393 leaf_index.tls_serialized_len()
1394 + generation.tls_serialized_len()
1395 + key_package_index.tls_serialized_len(),
1396 );
1397 leaf_index.tls_serialize(&mut out)?;
1398 generation.tls_serialize(&mut out)?;
1399 key_package_index.tls_serialize(&mut out)?;
1400 Ok(out)
1401 }
1402 }
1403 }
1404
1405 fn deserialize_for_operation(
1411 bytes: &[u8],
1412 operation_type: VirtualClientOperationType,
1413 ) -> Result<Self, VirtualClientsError> {
1414 let (leaf_index, rest) = LeafNodeIndex::tls_deserialize_bytes(bytes)?;
1415 let (generation, rest) = u32::tls_deserialize_bytes(rest)?;
1416 let (tbe, rest) = match operation_type {
1417 VirtualClientOperationType::KeyPackage => {
1418 let (key_package_index, rest) = u32::tls_deserialize_bytes(rest)?;
1419 (
1420 Self::KeyPackage {
1421 leaf_index,
1422 generation,
1423 key_package_index,
1424 },
1425 rest,
1426 )
1427 }
1428 VirtualClientOperationType::LeafNode => {
1432 let (external_init_secret, rest) =
1433 Option::<ExternalInitSecret>::tls_deserialize_bytes(rest)?;
1434 (
1435 Self::LeafNode {
1436 leaf_index,
1437 generation,
1438 external_init_secret,
1439 },
1440 rest,
1441 )
1442 }
1443 VirtualClientOperationType::Application => {
1444 return Err(VirtualClientsError::DerivationInfoMalformed);
1445 }
1446 };
1447 if !rest.is_empty() {
1448 return Err(VirtualClientsError::DerivationInfoMalformed);
1449 }
1450 Ok(tbe)
1451 }
1452}
1453
1454pub(crate) fn load_vc_epoch_state_and_tree<Provider: OpenMlsProvider>(
1460 provider: &Provider,
1461 epoch_id: &EpochId,
1462) -> Result<
1463 (
1464 EmulationEpochState,
1465 crate::components::vc_operation_tree::OperationSecretTree,
1466 ),
1467 VirtualClientsError,
1468> {
1469 use openmls_traits::storage::StorageProvider as _;
1470
1471 let storage = provider.storage();
1472 let state = storage
1473 .vc_emulation_epoch_state(epoch_id)
1474 .map_err(|e| {
1475 log::error!("vc: load emulation epoch state failed: {e:?}");
1476 VirtualClientsError::StorageError
1477 })?
1478 .ok_or(VirtualClientsError::MissingEmulationEpochState)?;
1479 let operation_tree = storage
1480 .vc_operation_tree(epoch_id)
1481 .map_err(|e| {
1482 log::error!("vc: load operation tree failed: {e:?}");
1483 VirtualClientsError::StorageError
1484 })?
1485 .ok_or(VirtualClientsError::MissingOperationTree)?;
1486 Ok((state, operation_tree))
1487}
1488
1489pub(crate) fn resolve_vc_leaf_dictionary(
1501 caller_capabilities: Option<&crate::treesync::node::leaf_node::Capabilities>,
1502 caller_extensions: Option<
1503 &crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
1504 >,
1505 current_leaf: Option<&crate::treesync::node::leaf_node::LeafNode>,
1506) -> Result<crate::extensions::AppDataDictionary, VirtualClientsError> {
1507 use crate::{
1508 component::{ComponentId, ComponentType},
1509 extensions::ExtensionType,
1510 };
1511 use tls_codec::DeserializeBytes as _;
1512
1513 let supports_app_data_dictionary = match caller_capabilities {
1514 Some(c) => c.extensions().contains(&ExtensionType::AppDataDictionary),
1515 None => current_leaf
1516 .map(|leaf| {
1517 leaf.capabilities()
1518 .extensions()
1519 .contains(&ExtensionType::AppDataDictionary)
1520 })
1521 .unwrap_or(false),
1522 };
1523 if !supports_app_data_dictionary {
1524 return Err(VirtualClientsError::AppDataDictionaryNotSupported);
1525 }
1526
1527 let mut resolved_dictionary = current_leaf
1528 .and_then(|leaf| leaf.extensions().app_data_dictionary())
1529 .map(|ext| ext.dictionary().clone())
1530 .unwrap_or_default();
1531 if let Some(caller_dict) = caller_extensions.and_then(|exts| exts.app_data_dictionary()) {
1532 for entry in caller_dict.dictionary().entries() {
1533 resolved_dictionary.insert(entry.id(), entry.data().to_vec());
1534 }
1535 }
1536
1537 let app_components_bytes = resolved_dictionary
1538 .get(&ComponentId::from(ComponentType::AppComponents))
1539 .map(<[u8]>::to_vec);
1540 let Some(app_components_bytes) = app_components_bytes else {
1541 return Err(VirtualClientsError::VcComponentNotListed);
1542 };
1543
1544 let supported_components = Vec::<u16>::tls_deserialize_exact_bytes(&app_components_bytes)
1547 .map_err(|e| {
1548 log::error!("vc: AppComponents body failed to deserialize: {e:?}");
1549 VirtualClientsError::VcComponentNotListed
1550 })?;
1551 if !supported_components.contains(&VC_COMPONENT_ID) {
1552 return Err(VirtualClientsError::VcComponentNotListed);
1553 }
1554
1555 Ok(resolved_dictionary)
1556}
1557
1558pub(crate) fn merge_vc_derivation_info(
1566 caller_extensions: Option<
1567 &crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
1568 >,
1569 mut resolved_dictionary: crate::extensions::AppDataDictionary,
1570 derivation_info_bytes: Vec<u8>,
1571) -> Result<
1572 crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
1573 crate::error::LibraryError,
1574> {
1575 use crate::extensions::{AppDataDictionaryExtension, Extension, Extensions};
1576
1577 resolved_dictionary.insert(VC_COMPONENT_ID, derivation_info_bytes);
1578 let vc_extension =
1579 Extension::AppDataDictionary(AppDataDictionaryExtension::new(resolved_dictionary));
1580
1581 let other_extensions = caller_extensions
1582 .map(|exts| {
1583 exts.iter()
1584 .filter(|ext| !matches!(ext, Extension::AppDataDictionary(_)))
1585 .cloned()
1586 .collect::<Vec<_>>()
1587 })
1588 .unwrap_or_default();
1589 let new_extensions: Vec<Extension> = other_extensions
1590 .into_iter()
1591 .chain(std::iter::once(vc_extension))
1592 .collect();
1593 Extensions::from_vec(new_extensions)
1594 .map_err(|_| crate::error::LibraryError::custom("Failed to build VC leaf-node extensions"))
1595}
1596
1597#[cfg(test)]
1598mod tests {
1599 use super::*;
1600 use openmls_rust_crypto::{MemoryStorage, OpenMlsRustCrypto};
1601 use openmls_traits::{
1602 random::OpenMlsRand,
1603 storage::{StorageProvider, CURRENT_VERSION},
1604 OpenMlsProvider,
1605 };
1606
1607 const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
1608
1609 fn register_epoch_state(provider: &OpenMlsRustCrypto, leaf_index: LeafNodeIndex) -> EpochId {
1613 use crate::components::vc_operation_tree::OperationSecretTree;
1614
1615 let emulator = EmulatorEpochSecret::new(
1616 &provider
1617 .rand()
1618 .random_vec(CIPHERSUITE.hash_length())
1619 .expect("randomness"),
1620 );
1621 let epoch_id = emulator
1622 .derive_epoch_id(provider.crypto(), CIPHERSUITE)
1623 .expect("derive epoch id");
1624 let epoch_encryption_key = emulator
1625 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
1626 .expect("derive epoch encryption key");
1627 let reuse_guard_secret = emulator
1628 .derive_reuse_guard_secret(provider.crypto(), CIPHERSUITE)
1629 .expect("derive reuse guard secret");
1630 let generation_id_secret = emulator
1631 .derive_generation_id_secret(provider.crypto(), CIPHERSUITE)
1632 .expect("derive generation id secret");
1633 let epoch_base_secret = emulator
1634 .derive_epoch_base_secret(provider.crypto(), CIPHERSUITE)
1635 .expect("derive epoch base secret");
1636 let emulation_group_size = TreeSize::new(2);
1637 let state = EmulationEpochState::new(
1638 leaf_index,
1639 epoch_encryption_key,
1640 reuse_guard_secret,
1641 generation_id_secret,
1642 emulation_group_size,
1643 CIPHERSUITE,
1644 );
1645 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_emulation_epoch_state(
1646 provider.storage(),
1647 &epoch_id,
1648 &state,
1649 )
1650 .expect("write emulation epoch state");
1651 let operation_tree = OperationSecretTree::new(epoch_base_secret, emulation_group_size);
1652 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_operation_tree(
1653 provider.storage(),
1654 &epoch_id,
1655 &operation_tree,
1656 )
1657 .expect("write operation tree");
1658 epoch_id
1659 }
1660
1661 #[test]
1664 fn assemble_upload_reads_leaf_index_from_state() {
1665 let provider = OpenMlsRustCrypto::default();
1666 let leaf_index = LeafNodeIndex::new(5);
1667 let epoch_id = register_epoch_state(&provider, leaf_index);
1668 let infos = vec![
1669 KeyPackageInfo {
1670 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
1671 cipher_suite: CIPHERSUITE,
1672 key_package_index: 0,
1673 },
1674 KeyPackageInfo {
1675 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
1676 cipher_suite: CIPHERSUITE,
1677 key_package_index: 1,
1678 },
1679 ];
1680
1681 let upload = assemble_vc_key_package_upload(provider.storage(), epoch_id.clone(), 4, infos)
1682 .expect("assemble upload");
1683
1684 assert_eq!(upload.epoch_id, epoch_id);
1685 assert_eq!(upload.leaf_index, leaf_index);
1686 assert_eq!(upload.generation, 4);
1687 assert_eq!(upload.key_package_info.len(), 2);
1688 }
1689
1690 #[test]
1693 fn assemble_upload_without_state_fails() {
1694 let provider = OpenMlsRustCrypto::default();
1695 let epoch_id = EpochId(b"unregistered-epoch".to_vec().into());
1696 let err = assemble_vc_key_package_upload(provider.storage(), epoch_id, 0, Vec::new())
1697 .expect_err("assemble must fail without registered state");
1698 assert_eq!(err, VirtualClientsError::MissingEmulationEpochState);
1699 }
1700
1701 #[test]
1705 fn process_upload_stores_records() {
1706 let provider = OpenMlsRustCrypto::default();
1707 let leaf_index = LeafNodeIndex::new(0);
1708 let epoch_id = register_epoch_state(&provider, leaf_index);
1709 let ref_a = KeyPackageRef::from_slice(b"kp-ref-a");
1710 let ref_b = KeyPackageRef::from_slice(b"kp-ref-b");
1711 let upload = KeyPackageUpload {
1712 epoch_id: epoch_id.clone(),
1713 leaf_index,
1714 generation: 0,
1715 key_package_info: vec![
1716 KeyPackageInfo {
1717 key_package_ref: ref_a.clone(),
1718 cipher_suite: CIPHERSUITE,
1719 key_package_index: 0,
1720 },
1721 KeyPackageInfo {
1722 key_package_ref: ref_b.clone(),
1723 cipher_suite: CIPHERSUITE,
1724 key_package_index: 1,
1725 },
1726 ],
1727 };
1728
1729 process_vc_key_package_upload(&provider, &upload).expect("process upload");
1730
1731 let material_a: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
1732 CURRENT_VERSION,
1733 >>::retained_key_package_material(
1734 provider.storage(), &ref_a
1735 )
1736 .expect("read material a")
1737 .expect("material a present");
1738 assert_eq!(material_a.epoch_id, epoch_id);
1739 assert_eq!(material_a.leaf_index, leaf_index);
1740 assert_eq!(material_a.generation, 0);
1741 assert_eq!(material_a.key_package_index, 0);
1742 assert_eq!(material_a.key_package_ciphersuite, CIPHERSUITE);
1743
1744 let material_b: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
1745 CURRENT_VERSION,
1746 >>::retained_key_package_material(
1747 provider.storage(), &ref_b
1748 )
1749 .expect("read material b")
1750 .expect("material b present");
1751 assert_eq!(material_b.epoch_id, epoch_id);
1752 assert_eq!(material_b.leaf_index, leaf_index);
1753 assert_eq!(material_b.generation, 0);
1754 assert_eq!(material_b.key_package_index, 1);
1755 assert_eq!(material_b.key_package_ciphersuite, CIPHERSUITE);
1756 }
1757
1758 #[test]
1760 fn delete_key_package_removes_vc_record() {
1761 let provider = OpenMlsRustCrypto::default();
1762 let leaf_index = LeafNodeIndex::new(0);
1763 let epoch_id = register_epoch_state(&provider, leaf_index);
1764 let kp_ref = KeyPackageRef::from_slice(b"kp-ref");
1765 let upload = KeyPackageUpload {
1766 epoch_id,
1767 leaf_index,
1768 generation: 0,
1769 key_package_info: vec![KeyPackageInfo {
1770 key_package_ref: kp_ref.clone(),
1771 cipher_suite: CIPHERSUITE,
1772 key_package_index: 0,
1773 }],
1774 };
1775 process_vc_key_package_upload(&provider, &upload).expect("process upload");
1776
1777 let present: Option<RetainedKeyPackageMaterial> = <MemoryStorage as StorageProvider<
1778 CURRENT_VERSION,
1779 >>::retained_key_package_material(
1780 provider.storage(), &kp_ref
1781 )
1782 .expect("read material");
1783 assert!(present.is_some());
1784
1785 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::delete_key_package(
1786 provider.storage(),
1787 &kp_ref,
1788 )
1789 .expect("delete key package");
1790
1791 let after: Option<RetainedKeyPackageMaterial> = <MemoryStorage as StorageProvider<
1792 CURRENT_VERSION,
1793 >>::retained_key_package_material(
1794 provider.storage(), &kp_ref
1795 )
1796 .expect("read material after delete");
1797 assert!(after.is_none());
1798 }
1799
1800 fn setup_key_and_epoch_id(provider: &OpenMlsRustCrypto) -> (EpochEncryptionKey, EpochId) {
1801 let emulator = EmulatorEpochSecret::new(
1802 &provider
1803 .rand()
1804 .random_vec(CIPHERSUITE.hash_length())
1805 .expect("randomness"),
1806 );
1807 let key = emulator
1808 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
1809 .expect("derive ek");
1810 let epoch_id = emulator
1811 .derive_epoch_id(provider.crypto(), CIPHERSUITE)
1812 .expect("derive epoch id");
1813 (key, epoch_id)
1814 }
1815
1816 #[test]
1822 fn derivation_info_tbe_roundtrip() {
1823 let provider = OpenMlsRustCrypto::default();
1824 let (key, epoch_id) = setup_key_and_epoch_id(&provider);
1825 let leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
1826
1827 let key_package_tbe = DerivationInfoTbe::KeyPackage {
1828 leaf_index: LeafNodeIndex::new(7),
1829 generation: 3,
1830 key_package_index: 5,
1831 };
1832 let leaf_node_tbe = DerivationInfoTbe::LeafNode {
1833 leaf_index: LeafNodeIndex::new(7),
1834 generation: 3,
1835 external_init_secret: None,
1836 };
1837 let external_commit_tbe = DerivationInfoTbe::LeafNode {
1838 leaf_index: LeafNodeIndex::new(7),
1839 generation: 3,
1840 external_init_secret: Some(ExternalInitSecret::from_slice(b"external init secret")),
1841 };
1842
1843 let key_package_bytes = key_package_tbe
1847 .tls_serialize_detached()
1848 .expect("serialize key package tbe");
1849 let leaf_node_bytes = leaf_node_tbe
1850 .tls_serialize_detached()
1851 .expect("serialize leaf node tbe");
1852 assert_eq!(key_package_bytes.len(), leaf_node_bytes.len() + 3);
1853
1854 for (original, operation_type) in [
1855 (key_package_tbe, VirtualClientOperationType::KeyPackage),
1856 (leaf_node_tbe, VirtualClientOperationType::LeafNode),
1857 (external_commit_tbe, VirtualClientOperationType::LeafNode),
1858 ] {
1859 let derivation_info = DerivationInfo::encrypt(
1860 provider.crypto(),
1861 CIPHERSUITE,
1862 &key,
1863 epoch_id.clone(),
1864 &leaf_encryption_key,
1865 &original,
1866 )
1867 .expect("encrypt");
1868 assert_eq!(derivation_info.epoch_id(), &epoch_id);
1869 let decrypted = derivation_info
1870 .decrypt(
1871 provider.crypto(),
1872 CIPHERSUITE,
1873 &key,
1874 &leaf_encryption_key,
1875 operation_type,
1876 )
1877 .expect("decrypt");
1878 assert_eq!(original, decrypted);
1879 }
1880 }
1881
1882 #[test]
1888 fn derivation_info_tbe_wire_format_matches_spec() {
1889 let absent = DerivationInfoTbe::LeafNode {
1890 leaf_index: LeafNodeIndex::new(7),
1891 generation: 3,
1892 external_init_secret: None,
1893 }
1894 .tls_serialize_detached()
1895 .expect("serialize");
1896 assert_eq!(
1897 absent,
1898 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00]
1899 );
1900
1901 let present = DerivationInfoTbe::LeafNode {
1902 leaf_index: LeafNodeIndex::new(7),
1903 generation: 3,
1904 external_init_secret: Some(ExternalInitSecret::from_slice(b"init")),
1905 }
1906 .tls_serialize_detached()
1907 .expect("serialize");
1908 assert_eq!(
1909 present,
1910 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x01, 0x04, b'i', b'n', b'i', b't']
1911 );
1912
1913 let key_package = DerivationInfoTbe::KeyPackage {
1914 leaf_index: LeafNodeIndex::new(7),
1915 generation: 3,
1916 key_package_index: 5,
1917 }
1918 .tls_serialize_detached()
1919 .expect("serialize");
1920 assert_eq!(
1921 key_package,
1922 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05]
1923 );
1924 }
1925
1926 #[test]
1931 fn derivation_info_tbe_rejects_trailing_data() {
1932 let variants = [
1933 (
1934 DerivationInfoTbe::LeafNode {
1935 leaf_index: LeafNodeIndex::new(7),
1936 generation: 3,
1937 external_init_secret: None,
1938 },
1939 VirtualClientOperationType::LeafNode,
1940 ),
1941 (
1942 DerivationInfoTbe::KeyPackage {
1943 leaf_index: LeafNodeIndex::new(7),
1944 generation: 3,
1945 key_package_index: 5,
1946 },
1947 VirtualClientOperationType::KeyPackage,
1948 ),
1949 ];
1950 for (tbe, operation_type) in variants {
1951 let mut bytes = tbe.tls_serialize_detached().expect("serialize");
1952 bytes.push(0x00);
1953 let result = DerivationInfoTbe::deserialize_for_operation(&bytes, operation_type);
1954 assert_eq!(result, Err(VirtualClientsError::DerivationInfoMalformed));
1955 }
1956 }
1957
1958 #[test]
1960 fn external_init_secret_debug_is_redacted() {
1961 let tbe = DerivationInfoTbe::LeafNode {
1962 leaf_index: LeafNodeIndex::new(7),
1963 generation: 3,
1964 external_init_secret: Some(ExternalInitSecret::from_slice(b"very secret bytes")),
1965 };
1966 let debug = format!("{tbe:?}");
1967 assert!(debug.contains("<redacted>"));
1968 assert!(!debug.contains("secret bytes"));
1969 assert!(!debug.to_lowercase().contains("76657279"));
1970 }
1971
1972 #[test]
1977 fn decryption_fails_with_wrong_leaf_encryption_key() {
1978 let provider = OpenMlsRustCrypto::default();
1979 let (key, epoch_id) = setup_key_and_epoch_id(&provider);
1980 let leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
1981 let tbe = DerivationInfoTbe::LeafNode {
1982 leaf_index: LeafNodeIndex::new(1),
1983 generation: 0,
1984 external_init_secret: None,
1985 };
1986 let derivation_info = DerivationInfo::encrypt(
1987 provider.crypto(),
1988 CIPHERSUITE,
1989 &key,
1990 epoch_id,
1991 &leaf_encryption_key,
1992 &tbe,
1993 )
1994 .expect("encrypt");
1995 let other_leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
1996 let err = derivation_info
1997 .decrypt(
1998 provider.crypto(),
1999 CIPHERSUITE,
2000 &key,
2001 &other_leaf_encryption_key,
2002 VirtualClientOperationType::LeafNode,
2003 )
2004 .expect_err("decryption with the wrong context must fail");
2005 assert_eq!(err, VirtualClientsError::DerivationInfoDecryptionFailed);
2006 }
2007
2008 #[test]
2012 fn key_package_seed_derivation_is_indexed_and_label_separated() {
2013 let provider = OpenMlsRustCrypto::default();
2014 let operation_secret = OperationSecret::from(Secret::from_slice(
2015 &provider
2016 .rand()
2017 .random_vec(CIPHERSUITE.hash_length())
2018 .expect("randomness"),
2019 ));
2020
2021 let seed_zero = operation_secret
2022 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2023 .expect("derive seed 0");
2024 let seed_zero_again = operation_secret
2025 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2026 .expect("derive seed 0 again");
2027 let seed_one = operation_secret
2028 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 1)
2029 .expect("derive seed 1");
2030
2031 let init_zero = seed_zero
2032 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2033 .expect("derive init key 0")
2034 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2035 .expect("generate init pair 0");
2036 let init_zero_again = seed_zero_again
2037 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2038 .expect("derive init key 0 again")
2039 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2040 .expect("generate init pair 0 again");
2041 let init_one = seed_one
2042 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2043 .expect("derive init key 1")
2044 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2045 .expect("generate init pair 1");
2046
2047 assert_eq!(init_zero.public, init_zero_again.public);
2049 assert_ne!(init_zero.public, init_one.public);
2051
2052 let encryption_zero = seed_zero
2054 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2055 .expect("derive encryption key 0")
2056 .generate_encryption_key_pair(provider.crypto(), CIPHERSUITE)
2057 .expect("generate encryption pair 0");
2058 assert_ne!(
2059 init_zero.public.as_slice(),
2060 encryption_zero.public_key().as_slice()
2061 );
2062 }
2063
2064 #[test]
2069 fn key_package_seed_binds_target_ciphersuite() {
2070 let provider = OpenMlsRustCrypto::default();
2071 let operation_secret = OperationSecret::from(Secret::from_slice(
2072 &provider
2073 .rand()
2074 .random_vec(CIPHERSUITE.hash_length())
2075 .expect("randomness"),
2076 ));
2077 let other_ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519;
2080
2081 let seed = operation_secret
2082 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2083 .expect("derive seed");
2084 let seed_other_suite = operation_secret
2085 .derive_key_package_seed_secret(provider.crypto(), other_ciphersuite, 0)
2086 .expect("derive seed under other target ciphersuite");
2087
2088 assert_ne!(seed.0.as_slice(), seed_other_suite.0.as_slice());
2089 }
2090
2091 #[test]
2096 fn target_operation_secret_binds_ciphersuite_and_group_id() {
2097 let provider = OpenMlsRustCrypto::default();
2098 let operation_secret = OperationSecret::from(Secret::from_slice(
2099 &provider
2100 .rand()
2101 .random_vec(CIPHERSUITE.hash_length())
2102 .expect("randomness"),
2103 ));
2104 let group_id = GroupId::from_slice(b"group-a");
2105 let other_ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519;
2106
2107 let target = operation_secret
2108 .derive_target_operation_secret(provider.crypto(), CIPHERSUITE, &group_id)
2109 .expect("derive target operation secret");
2110 let target_again = operation_secret
2111 .derive_target_operation_secret(provider.crypto(), CIPHERSUITE, &group_id)
2112 .expect("derive target operation secret again");
2113 let target_other_group = operation_secret
2114 .derive_target_operation_secret(
2115 provider.crypto(),
2116 CIPHERSUITE,
2117 &GroupId::from_slice(b"group-b"),
2118 )
2119 .expect("derive target operation secret for other group");
2120 let target_other_suite = operation_secret
2121 .derive_target_operation_secret(provider.crypto(), other_ciphersuite, &group_id)
2122 .expect("derive target operation secret under other target ciphersuite");
2123
2124 assert_eq!(target.0.as_slice(), target_again.0.as_slice());
2126 assert_ne!(target.0.as_slice(), target_other_group.0.as_slice());
2129 assert_ne!(target.0.as_slice(), target_other_suite.0.as_slice());
2130
2131 let encryption_key_secret = target
2134 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2135 .expect("derive encryption key secret");
2136 let path_generation_secret = target
2137 .derive_path_generation_secret(provider.crypto(), CIPHERSUITE)
2138 .expect("derive path generation secret");
2139 assert_ne!(
2140 encryption_key_secret.0.as_slice(),
2141 path_generation_secret.0.as_slice()
2142 );
2143 }
2144
2145 #[test]
2149 fn group_creation_secret_derivation_is_deterministic_and_label_separated() {
2150 let provider = OpenMlsRustCrypto::default();
2151 let operation_secret = OperationSecret::from(Secret::from_slice(
2152 &provider
2153 .rand()
2154 .random_vec(CIPHERSUITE.hash_length())
2155 .expect("randomness"),
2156 ));
2157
2158 let seed_zero = operation_secret
2159 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2160 .expect("derive seed 0");
2161 let seed_one = operation_secret
2162 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 1)
2163 .expect("derive seed 1");
2164
2165 let epoch_secret_zero = seed_zero
2166 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2167 .expect("derive group creation secret 0");
2168 let epoch_secret_zero_again = seed_zero
2169 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2170 .expect("derive group creation secret 0 again");
2171 let epoch_secret_one = seed_one
2172 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2173 .expect("derive group creation secret 1");
2174
2175 assert_eq!(
2177 epoch_secret_zero.as_slice(),
2178 epoch_secret_zero_again.as_slice()
2179 );
2180 assert_ne!(epoch_secret_zero.as_slice(), epoch_secret_one.as_slice());
2182
2183 let encryption_key_secret = seed_zero
2186 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2187 .expect("derive encryption key 0");
2188 assert_ne!(
2189 epoch_secret_zero.as_slice(),
2190 encryption_key_secret.0.as_slice()
2191 );
2192 }
2193
2194 #[test]
2197 fn validate_rejects_duplicate_index() {
2198 let infos = vec![
2199 KeyPackageInfo {
2200 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2201 cipher_suite: CIPHERSUITE,
2202 key_package_index: 2,
2203 },
2204 KeyPackageInfo {
2205 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2206 cipher_suite: CIPHERSUITE,
2207 key_package_index: 2,
2208 },
2209 ];
2210 let err = validate_key_package_infos(&infos).expect_err("duplicate index must be rejected");
2211 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageIndex(2));
2212 }
2213
2214 #[test]
2216 fn validate_rejects_duplicate_ref() {
2217 let infos = vec![
2218 KeyPackageInfo {
2219 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2220 cipher_suite: CIPHERSUITE,
2221 key_package_index: 0,
2222 },
2223 KeyPackageInfo {
2224 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2225 cipher_suite: CIPHERSUITE,
2226 key_package_index: 1,
2227 },
2228 ];
2229 let err = validate_key_package_infos(&infos).expect_err("duplicate ref must be rejected");
2230 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageRef);
2231 }
2232
2233 #[test]
2235 fn validate_accepts_distinct_infos() {
2236 let infos = vec![
2237 KeyPackageInfo {
2238 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2239 cipher_suite: CIPHERSUITE,
2240 key_package_index: 0,
2241 },
2242 KeyPackageInfo {
2243 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2244 cipher_suite: CIPHERSUITE,
2245 key_package_index: 1,
2246 },
2247 ];
2248 validate_key_package_infos(&infos).expect("distinct infos must pass");
2249 }
2250
2251 #[test]
2255 fn process_upload_rejects_malformed_without_consuming_generation() {
2256 let provider = OpenMlsRustCrypto::default();
2257 let leaf_index = LeafNodeIndex::new(0);
2258 let epoch_id = register_epoch_state(&provider, leaf_index);
2259 let ref_a = KeyPackageRef::from_slice(b"kp-ref-a");
2260 let ref_b = KeyPackageRef::from_slice(b"kp-ref-b");
2261
2262 let malformed = KeyPackageUpload {
2263 epoch_id: epoch_id.clone(),
2264 leaf_index,
2265 generation: 0,
2266 key_package_info: vec![
2267 KeyPackageInfo {
2268 key_package_ref: ref_a.clone(),
2269 cipher_suite: CIPHERSUITE,
2270 key_package_index: 0,
2271 },
2272 KeyPackageInfo {
2273 key_package_ref: ref_b.clone(),
2274 cipher_suite: CIPHERSUITE,
2275 key_package_index: 0,
2276 },
2277 ],
2278 };
2279 let err = process_vc_key_package_upload(&provider, &malformed)
2280 .expect_err("malformed upload must be rejected");
2281 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageIndex(0));
2282
2283 let valid = KeyPackageUpload {
2284 epoch_id: epoch_id.clone(),
2285 leaf_index,
2286 generation: 0,
2287 key_package_info: vec![
2288 KeyPackageInfo {
2289 key_package_ref: ref_a.clone(),
2290 cipher_suite: CIPHERSUITE,
2291 key_package_index: 0,
2292 },
2293 KeyPackageInfo {
2294 key_package_ref: ref_b.clone(),
2295 cipher_suite: CIPHERSUITE,
2296 key_package_index: 1,
2297 },
2298 ],
2299 };
2300 process_vc_key_package_upload(&provider, &valid)
2301 .expect("valid upload reusing the same generation must succeed");
2302
2303 let material_a: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2304 CURRENT_VERSION,
2305 >>::retained_key_package_material(
2306 provider.storage(), &ref_a
2307 )
2308 .expect("read material a")
2309 .expect("material a present");
2310 assert_eq!(material_a.epoch_id, epoch_id);
2311 assert_eq!(material_a.generation, 0);
2312 assert_eq!(material_a.key_package_index, 0);
2313
2314 let material_b: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2315 CURRENT_VERSION,
2316 >>::retained_key_package_material(
2317 provider.storage(), &ref_b
2318 )
2319 .expect("read material b")
2320 .expect("material b present");
2321 assert_eq!(material_b.key_package_index, 1);
2322 }
2323
2324 fn state_from_secret_bytes(
2327 provider: &OpenMlsRustCrypto,
2328 secret_bytes: &[u8],
2329 leaf_index: LeafNodeIndex,
2330 ) -> EmulationEpochState {
2331 let emulator = EmulatorEpochSecret::new(secret_bytes);
2332 let epoch_encryption_key = emulator
2333 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
2334 .expect("derive epoch encryption key");
2335 let reuse_guard_secret = emulator
2336 .derive_reuse_guard_secret(provider.crypto(), CIPHERSUITE)
2337 .expect("derive reuse guard secret");
2338 let generation_id_secret = emulator
2339 .derive_generation_id_secret(provider.crypto(), CIPHERSUITE)
2340 .expect("derive generation id secret");
2341 EmulationEpochState::new(
2342 leaf_index,
2343 epoch_encryption_key,
2344 reuse_guard_secret,
2345 generation_id_secret,
2346 TreeSize::new(2),
2347 CIPHERSUITE,
2348 )
2349 }
2350
2351 #[test]
2356 fn generation_id_is_deterministic_and_context_sensitive() {
2357 let provider = OpenMlsRustCrypto::default();
2358 let secret_bytes = provider
2359 .rand()
2360 .random_vec(CIPHERSUITE.hash_length())
2361 .expect("randomness");
2362 let state = state_from_secret_bytes(&provider, &secret_bytes, LeafNodeIndex::new(0));
2363
2364 let group_id = GroupId::from_slice(b"higher-level-group");
2365 let epoch = GroupEpoch::from(7);
2366 let derive = |group_id: &GroupId, epoch, generation, ratchet_type| {
2367 state
2368 .derive_generation_id(provider.crypto(), group_id, epoch, generation, ratchet_type)
2369 .expect("derive generation id")
2370 };
2371
2372 let base = derive(&group_id, epoch, 3, RatchetType::Application);
2373 assert_eq!(base.as_slice().len(), CIPHERSUITE.hash_length());
2375 assert_eq!(base, derive(&group_id, epoch, 3, RatchetType::Application));
2377 assert_ne!(base, derive(&group_id, epoch, 4, RatchetType::Application));
2380 assert_ne!(
2381 base,
2382 derive(&group_id, GroupEpoch::from(8), 3, RatchetType::Application)
2383 );
2384 assert_ne!(
2385 base,
2386 derive(
2387 &GroupId::from_slice(b"other-group"),
2388 epoch,
2389 3,
2390 RatchetType::Application
2391 )
2392 );
2393 assert_ne!(base, derive(&group_id, epoch, 3, RatchetType::Handshake));
2394
2395 let sibling = state_from_secret_bytes(&provider, &secret_bytes, LeafNodeIndex::new(5));
2399 let sibling_id = sibling
2400 .derive_generation_id(
2401 provider.crypto(),
2402 &group_id,
2403 epoch,
2404 3,
2405 RatchetType::Application,
2406 )
2407 .expect("sibling derive generation id");
2408 assert_eq!(base, sibling_id);
2409 }
2410}