1use std::collections::{BTreeSet, VecDeque};
2#[cfg(not(target_arch = "wasm32"))]
3use std::time::SystemTime;
4
5use openmls_traits::{
6 crypto::OpenMlsCrypto,
7 types::{Ciphersuite, CryptoError},
8 OpenMlsProvider,
9};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12use tls_codec::{
13 DeserializeBytes, SecretVLByteVec, Serialize as _, Size as _, TlsDeserializeBytes,
14 TlsSerialize, TlsSize, VLByteSlice, VLByteVec,
15};
16#[cfg(target_arch = "wasm32")]
17use web_time::SystemTime;
18
19use crate::{
20 binary_tree::{array_representation::TreeSize, LeafNodeIndex},
21 ciphersuite::{hash_ref::KeyPackageRef, Secret},
22 components::vc_operation_tree::OperationSecretTree,
23 extensions::{Extension, Extensions, LastResortExtension},
24 group::{
25 mls_group::errors::RegisterVcDerivationEpochError, GroupEpoch, GroupId,
26 VcDerivationEpochRetentionPolicy,
27 },
28 key_packages::{InitKey, KeyPackage},
29 messages::PathSecret,
30 schedule::application_export_tree::{ApplicationExportTree, ApplicationExportTreeError},
31 treesync::node::encryption_keys::EncryptionKeyPair,
32};
33
34pub const VC_COMPONENT_ID: u16 = 0x667A;
40
41const ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
48const PATH_GENERATION_LABEL: &str = "Path Generation";
49const INIT_KEY_LABEL: &str = "Init Key";
50const KEY_PACKAGE_SEED_LABEL: &str = "vc key package seed";
56const TARGET_OPERATION_LABEL: &str = "vc target operation";
59const GROUP_CREATION_LABEL: &str = "Group Creation";
62
63const DERIVATION_INFO_KEY_LABEL: &str = "key";
66const DERIVATION_INFO_NONCE_LABEL: &str = "nonce";
69
70const EPOCH_ID_LABEL: &str = "Epoch ID";
71const EPOCH_ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
72const EPOCH_BASE_SECRET_LABEL: &str = "Base Secret";
73const REUSE_GUARD_LABEL: &str = "Reuse Guard";
75const GENERATION_ID_LABEL: &str = "Generation ID Secret";
77const GENERATION_ID_EXPAND_LABEL: &str = "generation id";
81const REUSE_GUARD_PRP_KEY_LABEL: &str = "reuse guard";
84const PRP_KEY_LEN: usize = 16;
86
87#[derive(Error, Debug, PartialEq, Clone)]
89pub enum VirtualClientsError {
90 #[error("Failed to deserialize derivation info.")]
92 DerivationInfoMalformed,
93 #[error("Failed to decrypt derivation info.")]
96 DerivationInfoDecryptionFailed,
97 #[error("No virtual-clients operation secret tree for this epoch.")]
100 MissingOperationTree,
101 #[error("No virtual-clients derivation-epoch state for this epoch.")]
104 MissingDerivationEpochState,
105 #[error("No derivation epoch is registered for the group.")]
109 NoDerivationEpoch,
110 #[error("Virtual-clients storage error")]
113 StorageError,
114 #[error("Leaf encryption key from path does not match the derived key.")]
117 EncryptionKeyMismatch,
118 #[error("Cryptographic operation failed.")]
120 CryptoError(#[from] CryptoError),
121 #[error(
123 "Hash function produced output of length {actual_length}, expected {expected_length}."
124 )]
125 HashOutputLengthMismatch {
126 actual_length: usize,
128 expected_length: usize,
130 },
131 #[error("TLS codec error: {0}")]
135 Tls(#[from] tls_codec::Error),
136 #[error("Leaf does not declare AppDataDictionary support in its capabilities.")]
139 AppDataDictionaryNotSupported,
140 #[error("Leaf's AppComponents entry does not list the virtual-clients component id.")]
144 VcComponentNotListed,
145 #[error("Leaf index is outside the operation secret tree.")]
147 IndexOutOfBounds,
148 #[error("The operation coordinates name the caller's own leaf index.")]
151 OwnLeafIndex,
152 #[error("The operation secret for this generation was already consumed.")]
155 OperationGenerationConsumed,
156 #[error("The requested operation generation is too far beyond the ratchet head.")]
160 OperationGenerationTooDistant,
161 #[error("Operation ratchet generation has reached `u32::MAX`.")]
163 OperationRatchetTooLong,
164 #[error("An unrecoverable error has occurred due to a bug in the implementation.")]
167 LibraryError,
168 #[error("KeyPackageUpload contains a duplicate key_package_index: {0}.")]
171 DuplicateKeyPackageIndex(u32),
172 #[error("KeyPackageUpload contains a duplicate KeyPackageRef.")]
175 DuplicateKeyPackageRef,
176}
177
178#[derive(Debug, Serialize, Deserialize)]
182pub(crate) struct EmulatorEpochSecret(Secret);
183
184impl EmulatorEpochSecret {
185 pub(crate) fn new(bytes: &[u8]) -> Self {
189 Self(Secret::from_slice(bytes))
190 }
191
192 pub(crate) fn derive_epoch_id(
193 &self,
194 crypto: &impl OpenMlsCrypto,
195 ciphersuite: Ciphersuite,
196 ) -> Result<EpochId, VirtualClientsError> {
197 let secret = self.0.derive_secret(crypto, ciphersuite, EPOCH_ID_LABEL)?;
198 Ok(EpochId(secret.as_slice().to_vec().into()))
199 }
200
201 pub(crate) fn derive_epoch_encryption_key(
205 &self,
206 crypto: &impl OpenMlsCrypto,
207 ciphersuite: Ciphersuite,
208 ) -> Result<EpochEncryptionKey, VirtualClientsError> {
209 let secret = self
210 .0
211 .derive_secret(crypto, ciphersuite, EPOCH_ENCRYPTION_KEY_LABEL)?;
212 Ok(EpochEncryptionKey(secret))
213 }
214
215 pub(crate) fn derive_epoch_base_secret(
216 &self,
217 crypto: &impl OpenMlsCrypto,
218 ciphersuite: Ciphersuite,
219 ) -> Result<Secret, VirtualClientsError> {
220 Ok(self
221 .0
222 .derive_secret(crypto, ciphersuite, EPOCH_BASE_SECRET_LABEL)?)
223 }
224
225 pub(crate) fn derive_reuse_guard_secret(
227 &self,
228 crypto: &impl OpenMlsCrypto,
229 ciphersuite: Ciphersuite,
230 ) -> Result<ReuseGuardSecret, VirtualClientsError> {
231 let secret = self
232 .0
233 .derive_secret(crypto, ciphersuite, REUSE_GUARD_LABEL)?;
234 Ok(ReuseGuardSecret(secret))
235 }
236
237 pub(crate) fn derive_generation_id_secret(
239 &self,
240 crypto: &impl OpenMlsCrypto,
241 ciphersuite: Ciphersuite,
242 ) -> Result<GenerationIdSecret, VirtualClientsError> {
243 let secret = self
244 .0
245 .derive_secret(crypto, ciphersuite, GENERATION_ID_LABEL)?;
246 Ok(GenerationIdSecret(secret))
247 }
248}
249
250#[derive(Debug, Serialize, Deserialize)]
254pub(crate) struct ReuseGuardSecret(Secret);
255
256impl ReuseGuardSecret {
257 #[cfg(test)]
259 pub(crate) fn from_secret_for_tests(secret: Secret) -> Self {
260 Self(secret)
261 }
262
263 pub(crate) fn derive_prp_key(
273 &self,
274 crypto: &impl OpenMlsCrypto,
275 ciphersuite: Ciphersuite,
276 key_schedule_nonce: &[u8],
277 ) -> Result<[u8; PRP_KEY_LEN], VirtualClientsError> {
278 let key = self.0.kdf_expand_label(
279 crypto,
280 ciphersuite,
281 REUSE_GUARD_PRP_KEY_LABEL,
282 key_schedule_nonce,
283 PRP_KEY_LEN,
284 )?;
285 key.as_slice()
286 .try_into()
287 .map_err(|_| VirtualClientsError::HashOutputLengthMismatch {
288 actual_length: key.as_slice().len(),
289 expected_length: PRP_KEY_LEN,
290 })
291 }
292}
293
294#[derive(Debug, Serialize, Deserialize)]
299pub(crate) struct GenerationIdSecret(Secret);
300
301impl GenerationIdSecret {
302 fn derive_generation_id(
313 &self,
314 crypto: &impl OpenMlsCrypto,
315 ciphersuite: Ciphersuite,
316 context: &PrivateMessageContext<'_>,
317 ) -> Result<GenerationId, VirtualClientsError> {
318 let context_bytes = context.tls_serialize_detached()?;
319 let generation_id = self.0.kdf_expand_label(
320 crypto,
321 ciphersuite,
322 GENERATION_ID_EXPAND_LABEL,
323 &context_bytes,
324 ciphersuite.hash_length(),
325 )?;
326 Ok(GenerationId(generation_id.as_slice().to_vec().into()))
327 }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq, TlsSize, TlsSerialize)]
347#[repr(u8)]
348pub enum RatchetType {
349 Application = 1,
351 Handshake = 2,
353}
354
355#[derive(Debug, TlsSize, TlsSerialize)]
372pub(crate) struct PrivateMessageContext<'a> {
373 group_id: VLByteSlice<'a>,
374 epoch: u64,
375 generation: u32,
376 ratchet_type: RatchetType,
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
391pub struct GenerationId(VLByteVec);
392
393impl GenerationId {
394 pub fn as_slice(&self) -> &[u8] {
396 self.0.as_slice()
397 }
398}
399
400#[derive(Debug, TlsSize, TlsSerialize, TlsDeserializeBytes)]
416pub(crate) struct DerivationInfo {
417 epoch_id: EpochId,
418 ciphertext: VLByteVec,
419}
420
421impl DerivationInfo {
422 pub(crate) fn encrypt(
427 crypto: &impl OpenMlsCrypto,
428 ciphersuite: Ciphersuite,
429 key: &EpochEncryptionKey,
430 epoch_id: EpochId,
431 leaf_encryption_key: &[u8],
432 tbe: &DerivationInfoTbe,
433 ) -> Result<Self, VirtualClientsError> {
434 let (aead_key, aead_nonce) =
435 key.derive_key_nonce(crypto, ciphersuite, leaf_encryption_key)?;
436 let payload = tbe.tls_serialize_detached()?;
437 let ciphertext = crypto.aead_encrypt(
438 ciphersuite.aead_algorithm(),
439 aead_key.as_slice(),
440 payload.as_slice(),
441 aead_nonce.as_slice(),
442 epoch_id.0.as_slice(),
443 )?;
444 Ok(Self {
445 epoch_id,
446 ciphertext: ciphertext.into(),
447 })
448 }
449
450 pub(crate) fn epoch_id(&self) -> &EpochId {
451 &self.epoch_id
452 }
453
454 pub(crate) fn decrypt(
458 &self,
459 crypto: &impl OpenMlsCrypto,
460 ciphersuite: Ciphersuite,
461 key: &EpochEncryptionKey,
462 leaf_encryption_key: &[u8],
463 operation_type: VirtualClientOperationType,
464 ) -> Result<DerivationInfoTbe, VirtualClientsError> {
465 let (aead_key, aead_nonce) =
466 key.derive_key_nonce(crypto, ciphersuite, leaf_encryption_key)?;
467 let plaintext = crypto
468 .aead_decrypt(
469 ciphersuite.aead_algorithm(),
470 aead_key.as_slice(),
471 self.ciphertext.as_slice(),
472 aead_nonce.as_slice(),
473 self.epoch_id.0.as_slice(),
474 )
475 .map_err(|e| {
476 log::error!("vc: aead decrypt derivation info failed: {e:?}");
477 VirtualClientsError::DerivationInfoDecryptionFailed
478 })?;
479 DerivationInfoTbe::deserialize_for_operation(&plaintext, operation_type)
480 }
481}
482
483#[derive(
488 Debug,
489 Clone,
490 PartialEq,
491 Eq,
492 PartialOrd,
493 Ord,
494 Serialize,
495 Deserialize,
496 TlsSize,
497 TlsSerialize,
498 TlsDeserializeBytes,
499)]
500pub struct EpochId(VLByteVec);
501
502impl EpochId {
503 pub fn new(bytes: Vec<u8>) -> Self {
505 Self(bytes.into())
506 }
507
508 pub fn as_bytes(&self) -> &[u8] {
510 self.0.as_slice()
511 }
512}
513
514#[derive(Debug, PartialEq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
537pub struct KeyPackageInfo {
538 pub key_package_ref: KeyPackageRef,
540 pub cipher_suite: Ciphersuite,
542 pub key_package_index: u32,
544 pub extensions: Extensions<KeyPackage>,
546}
547
548#[derive(Debug, PartialEq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
571pub struct KeyPackageUpload {
572 pub epoch_id: EpochId,
574 pub leaf_index: LeafNodeIndex,
576 pub generation: u32,
578 pub key_package_info: Vec<KeyPackageInfo>,
580}
581
582#[derive(Debug, Serialize, Deserialize)]
604pub struct RetainedKeyPackageMaterial {
605 pub epoch_id: EpochId,
607 pub leaf_index: LeafNodeIndex,
610 pub generation: u32,
612 pub key_package_ciphersuite: Ciphersuite,
614 pub key_package_index: u32,
616 pub key_package_seed_secret: KeyPackageSeedSecret,
619 #[serde(default = "last_resort_extensions")]
623 pub key_package_extensions: Extensions<KeyPackage>,
624}
625
626fn last_resort_extensions() -> Extensions<KeyPackage> {
629 Extensions::single(Extension::LastResort(LastResortExtension::default()))
630 .expect("LastResort extensions are allowed in key packages")
631}
632
633fn validate_key_package_infos(infos: &[KeyPackageInfo]) -> Result<(), VirtualClientsError> {
643 let mut seen_indices = BTreeSet::new();
644 let mut seen_refs = BTreeSet::new();
645 for info in infos {
646 if !seen_indices.insert(info.key_package_index) {
647 return Err(VirtualClientsError::DuplicateKeyPackageIndex(
648 info.key_package_index,
649 ));
650 }
651 if !seen_refs.insert(&info.key_package_ref) {
652 return Err(VirtualClientsError::DuplicateKeyPackageRef);
653 }
654 }
655 Ok(())
656}
657
658#[openmls_traits::maybe_async]
677pub async fn assemble_vc_key_package_upload<Storage: crate::storage::StorageProvider>(
678 storage: &Storage,
679 epoch_id: EpochId,
680 generation: u32,
681 key_package_info: Vec<KeyPackageInfo>,
682) -> Result<KeyPackageUpload, VirtualClientsError> {
683 validate_key_package_infos(&key_package_info)?;
684 let state: VcDerivationEpochState = storage
685 .vc_derivation_epoch_state(&epoch_id)
686 .await
687 .map_err(|e| {
688 log::error!("vc: load derivation epoch state in assemble upload failed: {e:?}");
689 VirtualClientsError::StorageError
690 })?
691 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
692 Ok(KeyPackageUpload {
693 epoch_id,
694 leaf_index: state.leaf_index,
695 generation,
696 key_package_info,
697 })
698}
699
700#[openmls_traits::maybe_async]
715pub async fn process_vc_key_package_upload<Provider: OpenMlsProvider>(
716 provider: &Provider,
717 upload: &KeyPackageUpload,
718) -> Result<(), VirtualClientsError> {
719 use crate::components::vc_operation_tree::OperationSecretTree;
720 use openmls_traits::storage::StorageProvider as _;
721
722 validate_key_package_infos(&upload.key_package_info)?;
723
724 let storage = provider.storage();
725 let crypto = provider.crypto();
726
727 let state: VcDerivationEpochState = storage
728 .vc_derivation_epoch_state(&upload.epoch_id)
729 .await
730 .map_err(|e| {
731 log::error!("vc: load derivation epoch state in process upload failed: {e:?}");
732 VirtualClientsError::StorageError
733 })?
734 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
735 let mut operation_tree: OperationSecretTree = storage
736 .vc_operation_tree(&upload.epoch_id)
737 .await
738 .map_err(|e| {
739 log::error!("vc: load operation tree in process upload failed: {e:?}");
740 VirtualClientsError::StorageError
741 })?
742 .ok_or(VirtualClientsError::MissingOperationTree)?;
743 let emulation_ciphersuite = state.emulation_ciphersuite;
744
745 let operation_secret = operation_tree.derive_operation_secret(
747 crypto,
748 emulation_ciphersuite,
749 &upload.epoch_id,
750 upload.leaf_index,
751 VirtualClientOperationType::KeyPackage,
752 upload.generation,
753 b"",
754 )?;
755
756 let mut materials = Vec::with_capacity(upload.key_package_info.len());
757 for info in &upload.key_package_info {
758 let key_package_seed_secret = operation_secret.derive_key_package_seed_secret(
759 crypto,
760 info.cipher_suite,
761 info.key_package_index,
762 )?;
763 let material = RetainedKeyPackageMaterial {
764 epoch_id: upload.epoch_id.clone(),
765 leaf_index: upload.leaf_index,
766 generation: upload.generation,
767 key_package_ciphersuite: info.cipher_suite,
768 key_package_index: info.key_package_index,
769 key_package_seed_secret,
770 key_package_extensions: info.extensions.clone(),
771 };
772 materials.push((info.key_package_ref.clone(), material));
773 }
774
775 storage
776 .write_retained_key_package_material_batch(&upload.epoch_id, &operation_tree, &materials)
777 .await
778 .map_err(|e| {
779 log::error!("vc: persist batch key package material in process upload failed: {e:?}");
780 VirtualClientsError::StorageError
781 })?;
782 Ok(())
783}
784
785#[derive(Debug)]
796pub(crate) struct VcWelcomeMaterial {
797 pub(crate) key_package_ref: KeyPackageRef,
799 pub(crate) epoch_id: EpochId,
801 pub(crate) leaf_index: LeafNodeIndex,
804 pub(crate) generation: u32,
806 pub(crate) key_package_index: u32,
808 pub(crate) init_private_key: openmls_traits::types::HpkePrivateKey,
811 pub(crate) init_key: InitKey,
813 pub(crate) encryption_keypair: EncryptionKeyPair,
816 pub(crate) last_resort: bool,
818}
819
820#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
823pub struct VcDerivationEpochLogEntry {
824 pub(crate) sequence: u64,
826 pub(crate) group_epoch: GroupEpoch,
828 pub(crate) epoch_id: EpochId,
830 pub(crate) registered_at: SystemTime,
832}
833
834impl VcDerivationEpochLogEntry {
835 pub fn from_legacy_record(
838 group_epoch: GroupEpoch,
839 epoch_id: EpochId,
840 registered_at: SystemTime,
841 ) -> Self {
842 Self {
843 sequence: 0,
844 group_epoch,
845 epoch_id,
846 registered_at,
847 }
848 }
849
850 pub fn epoch_id(&self) -> &EpochId {
852 &self.epoch_id
853 }
854}
855
856#[derive(Debug, Default)]
861pub(crate) struct VcDerivationEpochLog {
862 entries: VecDeque<VcDerivationEpochLogEntry>,
864}
865
866impl VcDerivationEpochLog {
867 #[openmls_traits::maybe_async]
870 pub(crate) async fn load<Storage: crate::storage::StorageProvider>(
871 storage: &Storage,
872 group_id: &GroupId,
873 ) -> Result<Self, Storage::Error> {
874 let mut entries: Vec<VcDerivationEpochLogEntry> =
875 storage.vc_derivation_epoch_log_entries(group_id).await?;
876 entries.sort_unstable_by_key(|entry| entry.sequence);
877 debug_assert!(
881 entries
882 .iter()
883 .map(|entry| &entry.epoch_id)
884 .collect::<BTreeSet<_>>()
885 .len()
886 == entries.len(),
887 "duplicate derivation epoch id in log"
888 );
889 Ok(Self {
890 entries: entries.into(),
891 })
892 }
893
894 pub(crate) fn is_empty(&self) -> bool {
895 self.entries.is_empty()
896 }
897
898 pub(crate) fn newest(&self) -> Option<&VcDerivationEpochLogEntry> {
900 self.entries.back()
901 }
902
903 fn push(&mut self, group_epoch: GroupEpoch, epoch_id: EpochId) -> VcDerivationEpochLogEntry {
907 let sequence = self
908 .entries
909 .back()
910 .map_or(0, |entry| entry.sequence.saturating_add(1));
911 let entry = VcDerivationEpochLogEntry {
912 sequence,
913 group_epoch,
914 epoch_id,
915 registered_at: SystemTime::now(),
916 };
917 self.entries.push_back(entry.clone());
918 entry
919 }
920
921 pub(crate) fn shrink_to(&mut self, max_entries: usize) -> Vec<EpochId> {
925 let excess = self.entries.len().saturating_sub(max_entries.max(1));
926 self.drop_oldest(excess)
927 }
928
929 pub(crate) fn drop_superseded_before(&mut self, cutoff: SystemTime) -> Vec<EpochId> {
934 let count = self
935 .entries
936 .iter()
937 .skip(1)
938 .rposition(|successor| successor.registered_at < cutoff)
939 .map_or(0, |index| index + 1);
940 self.drop_oldest(count)
941 }
942
943 fn drop_oldest(&mut self, count: usize) -> Vec<EpochId> {
946 let droppable = self.entries.len().saturating_sub(1);
947 self.entries
948 .drain(0..count.min(droppable))
949 .map(|entry| entry.epoch_id)
950 .collect()
951 }
952}
953
954#[deprecated(
958 since = "0.9.0",
959 note = "migration-only: decode pre-log registration records and convert them with \
960 `VcDerivationEpochLogEntry::from_legacy_record`. Will be removed in 0.10.0."
961)]
962#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
963pub struct RegisteredVcDerivationEpoch {
964 pub group_epoch: GroupEpoch,
966 pub epoch_id: EpochId,
968}
969
970#[openmls_traits::maybe_async]
976pub(crate) async fn newest_vc_derivation_epoch<Storage: crate::storage::StorageProvider>(
977 storage: &Storage,
978 emulation_group_id: &GroupId,
979) -> Result<Option<EpochId>, Storage::Error> {
980 let entries: Vec<VcDerivationEpochLogEntry> = storage
981 .vc_derivation_epoch_log_entries(emulation_group_id)
982 .await?;
983 Ok(entries
984 .into_iter()
985 .max_by_key(|entry| entry.sequence)
986 .map(|entry| entry.epoch_id))
987}
988
989#[openmls_traits::maybe_async]
1002pub(crate) async fn require_newest_vc_derivation_epoch<Storage: crate::storage::StorageProvider>(
1003 storage: &Storage,
1004 emulation_group_id: &GroupId,
1005) -> Result<EpochId, VirtualClientsError> {
1006 newest_vc_derivation_epoch(storage, emulation_group_id)
1007 .await
1008 .map_err(|e| {
1009 log::error!("vc: load newest derivation epoch for a new operation failed: {e:?}");
1010 VirtualClientsError::StorageError
1011 })?
1012 .ok_or(VirtualClientsError::NoDerivationEpoch)
1013}
1014
1015pub(crate) struct VcDerivationEpochParams<'a> {
1019 pub(crate) group_id: &'a GroupId,
1021 pub(crate) ciphersuite: Ciphersuite,
1023 pub(crate) group_epoch: GroupEpoch,
1025 pub(crate) own_leaf_index: LeafNodeIndex,
1027 pub(crate) tree_size: TreeSize,
1029 pub(crate) retention_policy: VcDerivationEpochRetentionPolicy,
1031}
1032
1033impl<'a> VcDerivationEpochParams<'a> {
1034 pub(crate) fn for_public_group(
1041 public_group: &'a crate::group::PublicGroup,
1042 own_leaf_index: LeafNodeIndex,
1043 retention_policy: VcDerivationEpochRetentionPolicy,
1044 ) -> Self {
1045 Self {
1046 group_id: public_group.group_id(),
1047 ciphersuite: public_group.ciphersuite(),
1048 group_epoch: public_group.group_context().epoch(),
1049 own_leaf_index,
1050 tree_size: public_group.tree_size(),
1051 retention_policy,
1052 }
1053 }
1054}
1055
1056#[openmls_traits::maybe_async]
1084pub(crate) async fn register_vc_derivation_epoch<
1085 Crypto: OpenMlsCrypto,
1086 Storage: crate::storage::StorageProvider,
1087>(
1088 crypto: &Crypto,
1089 storage: &Storage,
1090 export_tree: Option<&mut ApplicationExportTree>,
1091 params: VcDerivationEpochParams<'_>,
1092) -> Result<EpochId, RegisterVcDerivationEpochError<Storage::Error>> {
1093 let VcDerivationEpochParams {
1094 group_id,
1095 ciphersuite,
1096 group_epoch,
1097 own_leaf_index,
1098 tree_size,
1099 retention_policy,
1100 } = params;
1101 let export_tree =
1102 export_tree.ok_or(RegisterVcDerivationEpochError::MissingApplicationExportTree)?;
1103
1104 let mut log = VcDerivationEpochLog::load(storage, group_id)
1105 .await
1106 .map_err(|e| {
1107 log::error!("vc: load derivation epoch log before registration failed: {e:?}");
1108 RegisterVcDerivationEpochError::Storage(e)
1109 })?;
1110
1111 let bytes = match export_tree.safe_export_secret(crypto, ciphersuite, VC_COMPONENT_ID) {
1116 Ok(bytes) => bytes,
1117 Err(ApplicationExportTreeError::PuncturedInput) => {
1118 if let Some(newest) = log.newest() {
1121 if newest.group_epoch == group_epoch {
1122 return Ok(newest.epoch_id.clone());
1123 }
1124 }
1125 return Err(RegisterVcDerivationEpochError::ApplicationExportTree(
1126 ApplicationExportTreeError::PuncturedInput,
1127 ));
1128 }
1129 Err(e) => return Err(e.into()),
1130 };
1131 let emulator_epoch_secret = EmulatorEpochSecret::new(bytes.as_slice());
1132 let epoch_id = emulator_epoch_secret.derive_epoch_id(crypto, ciphersuite)?;
1133 if let Some(newest) = log.newest() {
1134 if newest.group_epoch == group_epoch && newest.epoch_id == epoch_id {
1135 return Ok(newest.epoch_id.clone());
1140 }
1141 }
1142 let epoch_encryption_key =
1143 emulator_epoch_secret.derive_epoch_encryption_key(crypto, ciphersuite)?;
1144 let epoch_base_secret = emulator_epoch_secret.derive_epoch_base_secret(crypto, ciphersuite)?;
1145 let reuse_guard_secret =
1146 emulator_epoch_secret.derive_reuse_guard_secret(crypto, ciphersuite)?;
1147 let generation_id_secret =
1148 emulator_epoch_secret.derive_generation_id_secret(crypto, ciphersuite)?;
1149 let operation_tree = OperationSecretTree::new(epoch_base_secret, tree_size);
1150 let state = VcDerivationEpochState::new(
1151 own_leaf_index,
1152 epoch_encryption_key,
1153 reuse_guard_secret,
1154 generation_id_secret,
1155 tree_size,
1156 ciphersuite,
1157 );
1158 let entry = log.push(group_epoch, epoch_id.clone());
1159 let dropped = log.shrink_to(retention_policy.max_epochs().unwrap_or(usize::MAX));
1160
1161 storage
1162 .write_vc_operation_tree(&epoch_id, &operation_tree)
1163 .await
1164 .map_err(|e| {
1165 log::error!("vc: persist operation tree at registration failed: {e:?}");
1166 RegisterVcDerivationEpochError::Storage(e)
1167 })?;
1168 storage
1169 .write_vc_derivation_epoch_state(&epoch_id, &state)
1170 .await
1171 .map_err(|e| {
1172 log::error!("vc: persist derivation epoch state at registration failed: {e:?}");
1173 RegisterVcDerivationEpochError::Storage(e)
1174 })?;
1175 storage
1176 .write_vc_derivation_epoch_log_entry(group_id, &epoch_id, &entry)
1177 .await
1178 .map_err(|e| {
1179 log::error!("vc: persist derivation epoch log entry at registration failed: {e:?}");
1180 RegisterVcDerivationEpochError::Storage(e)
1181 })?;
1182 if !dropped.is_empty() {
1183 storage
1184 .delete_vc_derivation_epoch_log_entries(group_id, &dropped)
1185 .await
1186 .map_err(|e| {
1187 log::error!("vc: prune derivation epoch log at registration failed: {e:?}");
1188 RegisterVcDerivationEpochError::Storage(e)
1189 })?;
1190 }
1191 storage
1195 .delete_unreferenced_vc_derivation_epoch_states::<EpochId>()
1196 .await
1197 .map_err(|e| {
1198 log::error!("vc: release pruned derivation epochs at registration failed: {e:?}");
1199 RegisterVcDerivationEpochError::Storage(e)
1200 })?;
1201
1202 Ok(epoch_id)
1203}
1204
1205#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1214pub struct VcEmulationBinding {
1215 pub(crate) group_epoch: GroupEpoch,
1217 pub(crate) epoch_id: EpochId,
1219}
1220
1221impl VcEmulationBinding {
1222 pub fn from_legacy_record(group_epoch: GroupEpoch, epoch_id: EpochId) -> Self {
1225 Self {
1226 group_epoch,
1227 epoch_id,
1228 }
1229 }
1230
1231 pub fn epoch_id(&self) -> &EpochId {
1233 &self.epoch_id
1234 }
1235
1236 pub(crate) fn into_epoch_id(self) -> EpochId {
1237 self.epoch_id
1238 }
1239}
1240
1241#[deprecated(
1245 since = "0.9.0",
1246 note = "migration-only: decode pre-row bindings records and convert their entries with \
1247 `VcEmulationBinding::from_legacy_record`. Will be removed in 0.10.0."
1248)]
1249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1250pub struct VcEmulationBindings {
1251 bindings: VecDeque<(GroupEpoch, EpochId)>,
1253}
1254
1255#[allow(deprecated)]
1256impl VcEmulationBindings {
1257 pub fn into_entries(self) -> Vec<(GroupEpoch, EpochId)> {
1259 self.bindings.into()
1260 }
1261}
1262
1263#[openmls_traits::maybe_async]
1269pub(crate) async fn write_vc_emulation_binding_with_pruning<
1270 Storage: crate::storage::StorageProvider,
1271>(
1272 storage: &Storage,
1273 group_id: &GroupId,
1274 group_epoch: GroupEpoch,
1275 epoch_id: EpochId,
1276 max_entries: usize,
1277) -> Result<(), Storage::Error> {
1278 let binding = VcEmulationBinding {
1279 group_epoch,
1280 epoch_id: epoch_id.clone(),
1281 };
1282 storage
1283 .write_vc_emulation_binding(group_id, &group_epoch, &epoch_id, &binding)
1284 .await?;
1285 let mut bindings: Vec<VcEmulationBinding> = storage.vc_emulation_bindings(group_id).await?;
1286 if bindings.len() > max_entries {
1287 bindings.sort_unstable_by_key(|binding| binding.group_epoch.as_u64());
1288 let stale: Vec<GroupEpoch> = bindings[..bindings.len() - max_entries]
1289 .iter()
1290 .map(|binding| binding.group_epoch)
1291 .collect();
1292 storage
1293 .delete_vc_emulation_bindings(group_id, &stale)
1294 .await?;
1295 }
1296 Ok(())
1297}
1298
1299#[derive(Debug, Serialize, Deserialize)]
1316pub(crate) struct EpochEncryptionKey(Secret);
1317
1318impl EpochEncryptionKey {
1319 fn derive_key_nonce(
1323 &self,
1324 crypto: &impl OpenMlsCrypto,
1325 ciphersuite: Ciphersuite,
1326 leaf_encryption_key: &[u8],
1327 ) -> Result<(Secret, Secret), VirtualClientsError> {
1328 let key = self.0.kdf_expand_label(
1329 crypto,
1330 ciphersuite,
1331 DERIVATION_INFO_KEY_LABEL,
1332 leaf_encryption_key,
1333 ciphersuite.aead_key_length(),
1334 )?;
1335 let nonce = self.0.kdf_expand_label(
1336 crypto,
1337 ciphersuite,
1338 DERIVATION_INFO_NONCE_LABEL,
1339 leaf_encryption_key,
1340 ciphersuite.aead_nonce_length(),
1341 )?;
1342 Ok((key, nonce))
1343 }
1344}
1345
1346#[derive(Debug, Serialize, Deserialize)]
1357pub struct VcDerivationEpochState {
1358 pub(crate) leaf_index: LeafNodeIndex,
1362 pub(crate) epoch_encryption_key: EpochEncryptionKey,
1363 pub(crate) reuse_guard_secret: ReuseGuardSecret,
1364 pub(crate) generation_id_secret: GenerationIdSecret,
1367 pub(crate) emulation_group_size: TreeSize,
1369 pub(crate) emulation_ciphersuite: Ciphersuite,
1372}
1373
1374impl VcDerivationEpochState {
1375 pub(crate) fn new(
1376 leaf_index: LeafNodeIndex,
1377 epoch_encryption_key: EpochEncryptionKey,
1378 reuse_guard_secret: ReuseGuardSecret,
1379 generation_id_secret: GenerationIdSecret,
1380 emulation_group_size: TreeSize,
1381 emulation_ciphersuite: Ciphersuite,
1382 ) -> Self {
1383 Self {
1384 leaf_index,
1385 epoch_encryption_key,
1386 reuse_guard_secret,
1387 generation_id_secret,
1388 emulation_group_size,
1389 emulation_ciphersuite,
1390 }
1391 }
1392
1393 pub(crate) fn into_parts(self) -> (LeafNodeIndex, EpochEncryptionKey, Ciphersuite) {
1396 (
1397 self.leaf_index,
1398 self.epoch_encryption_key,
1399 self.emulation_ciphersuite,
1400 )
1401 }
1402
1403 pub(crate) fn derive_generation_id(
1409 &self,
1410 crypto: &impl OpenMlsCrypto,
1411 group_id: &GroupId,
1412 epoch: GroupEpoch,
1413 generation: u32,
1414 ratchet_type: RatchetType,
1415 ) -> Result<GenerationId, VirtualClientsError> {
1416 let context = PrivateMessageContext {
1417 group_id: VLByteSlice(group_id.as_slice()),
1418 epoch: epoch.as_u64(),
1419 generation,
1420 ratchet_type,
1421 };
1422 self.generation_id_secret
1423 .derive_generation_id(crypto, self.emulation_ciphersuite, &context)
1424 }
1425
1426 pub(crate) fn reuse_guard_inputs(&self) -> crate::framing::EmulatorReuseGuardCtx<'_> {
1429 crate::framing::EmulatorReuseGuardCtx {
1430 reuse_guard_secret: &self.reuse_guard_secret,
1431 emulation_ciphersuite: self.emulation_ciphersuite,
1432 emulation_group_size: self.emulation_group_size,
1433 emulation_leaf_index: self.leaf_index,
1434 }
1435 }
1436}
1437
1438#[derive(Debug, Serialize, Deserialize)]
1446pub struct OperationSecret(Secret);
1447
1448impl From<Secret> for OperationSecret {
1449 fn from(secret: Secret) -> Self {
1450 Self(secret)
1451 }
1452}
1453
1454fn import_secret(
1458 crypto: &impl OpenMlsCrypto,
1459 target_ciphersuite: Ciphersuite,
1460 source_secret: &Secret,
1461 label: &str,
1462 context: &[u8],
1463) -> Result<Secret, CryptoError> {
1464 let salt = Secret::from_slice(&[]);
1465 let target_prk = salt.hkdf_extract(crypto, target_ciphersuite, source_secret)?;
1466 target_prk.kdf_expand_label(
1467 crypto,
1468 target_ciphersuite,
1469 label,
1470 context,
1471 target_ciphersuite.hash_length(),
1472 )
1473}
1474
1475impl OperationSecret {
1476 pub(crate) fn as_slice(&self) -> &[u8] {
1478 self.0.as_slice()
1479 }
1480
1481 pub(crate) fn derive_target_operation_secret(
1497 &self,
1498 crypto: &impl OpenMlsCrypto,
1499 target_ciphersuite: Ciphersuite,
1500 group_id: &GroupId,
1501 ) -> Result<TargetOperationSecret, VirtualClientsError> {
1502 let context = TargetOperationContext {
1503 cipher_suite: target_ciphersuite,
1504 group_id: VLByteSlice(group_id.as_slice()),
1505 }
1506 .tls_serialize_detached()?;
1507 let secret = import_secret(
1508 crypto,
1509 target_ciphersuite,
1510 &self.0,
1511 TARGET_OPERATION_LABEL,
1512 &context,
1513 )?;
1514 Ok(TargetOperationSecret(secret))
1515 }
1516
1517 pub(crate) fn derive_key_package_seed_secret(
1531 &self,
1532 crypto: &impl OpenMlsCrypto,
1533 target_ciphersuite: Ciphersuite,
1534 key_package_index: u32,
1535 ) -> Result<KeyPackageSeedSecret, VirtualClientsError> {
1536 let context = KeyPackageSeedContext {
1537 cipher_suite: target_ciphersuite,
1538 key_package_index,
1539 }
1540 .tls_serialize_detached()?;
1541 let seed = import_secret(
1542 crypto,
1543 target_ciphersuite,
1544 &self.0,
1545 KEY_PACKAGE_SEED_LABEL,
1546 &context,
1547 )?;
1548 Ok(KeyPackageSeedSecret(seed))
1549 }
1550}
1551
1552#[derive(Debug, TlsSize, TlsSerialize)]
1565struct KeyPackageSeedContext {
1566 cipher_suite: Ciphersuite,
1567 key_package_index: u32,
1568}
1569
1570#[derive(Debug, Serialize, Deserialize)]
1577pub struct KeyPackageSeedSecret(Secret);
1578
1579impl KeyPackageSeedSecret {
1580 pub(crate) fn derive_init_key_secret(
1581 &self,
1582 crypto: &impl OpenMlsCrypto,
1583 ciphersuite: Ciphersuite,
1584 ) -> Result<InitKeySecret, VirtualClientsError> {
1585 let init_key_secret = self.0.derive_secret(crypto, ciphersuite, INIT_KEY_LABEL)?;
1586 Ok(InitKeySecret(init_key_secret))
1587 }
1588
1589 pub(crate) fn derive_encryption_key_secret(
1590 &self,
1591 crypto: &impl OpenMlsCrypto,
1592 ciphersuite: Ciphersuite,
1593 ) -> Result<EncryptionKeySecret, VirtualClientsError> {
1594 let encryption_key_secret =
1595 self.0
1596 .derive_secret(crypto, ciphersuite, ENCRYPTION_KEY_LABEL)?;
1597 Ok(EncryptionKeySecret(encryption_key_secret))
1598 }
1599
1600 pub(crate) fn derive_group_creation_secret(
1611 &self,
1612 crypto: &impl OpenMlsCrypto,
1613 ciphersuite: Ciphersuite,
1614 ) -> Result<Secret, VirtualClientsError> {
1615 Ok(self
1616 .0
1617 .derive_secret(crypto, ciphersuite, GROUP_CREATION_LABEL)?)
1618 }
1619}
1620
1621pub(crate) struct EncryptionKeySecret(Secret);
1622
1623impl EncryptionKeySecret {
1624 pub(crate) fn generate_encryption_key_pair(
1625 &self,
1626 crypto: &impl OpenMlsCrypto,
1627 ciphersuite: Ciphersuite,
1628 ) -> Result<EncryptionKeyPair, VirtualClientsError> {
1629 let hpke_config = ciphersuite.hpke_config();
1630 let key_pair = crypto.derive_hpke_keypair(hpke_config, self.0.as_slice())?;
1631 Ok(EncryptionKeyPair::from(key_pair))
1632 }
1633}
1634
1635pub(crate) struct InitKeySecret(Secret);
1636
1637impl InitKeySecret {
1638 pub(crate) fn generate_init_key_pair(
1639 &self,
1640 crypto: &impl OpenMlsCrypto,
1641 ciphersuite: Ciphersuite,
1642 ) -> Result<openmls_traits::types::HpkeKeyPair, VirtualClientsError> {
1643 let hpke_config = ciphersuite.hpke_config();
1644 let key_pair = crypto.derive_hpke_keypair(hpke_config, self.0.as_slice())?;
1645 Ok(key_pair)
1646 }
1647}
1648
1649pub(crate) struct PathGenerationSecret(Secret);
1650
1651impl From<PathGenerationSecret> for PathSecret {
1652 fn from(value: PathGenerationSecret) -> Self {
1653 value.0.into()
1654 }
1655}
1656
1657#[derive(Debug, Clone, Copy, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
1674#[repr(u8)]
1675pub enum VirtualClientOperationType {
1676 KeyPackage = 1,
1678 LeafNode = 2,
1681 Application = 3,
1683}
1684
1685#[derive(Clone, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
1698pub(crate) struct ExternalInitSecret(SecretVLByteVec);
1699
1700impl std::fmt::Debug for ExternalInitSecret {
1701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1702 f.debug_struct("ExternalInitSecret")
1703 .field("init_secret", &"<redacted>")
1704 .finish()
1705 }
1706}
1707
1708impl ExternalInitSecret {
1709 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1710 Self(bytes.to_vec().into())
1711 }
1712
1713 pub(crate) fn as_slice(&self) -> &[u8] {
1714 self.0.as_slice()
1715 }
1716}
1717
1718#[derive(Debug, TlsSize, TlsSerialize)]
1725struct TargetOperationContext<'a> {
1726 cipher_suite: Ciphersuite,
1727 group_id: VLByteSlice<'a>,
1728}
1729
1730#[derive(Debug)]
1734pub(crate) struct TargetOperationSecret(Secret);
1735
1736impl TargetOperationSecret {
1737 pub(crate) fn derive_encryption_key_secret(
1738 &self,
1739 crypto: &impl OpenMlsCrypto,
1740 ciphersuite: Ciphersuite,
1741 ) -> Result<EncryptionKeySecret, VirtualClientsError> {
1742 let encryption_key_secret =
1743 self.0
1744 .derive_secret(crypto, ciphersuite, ENCRYPTION_KEY_LABEL)?;
1745 Ok(EncryptionKeySecret(encryption_key_secret))
1746 }
1747
1748 pub(crate) fn derive_path_generation_secret(
1749 &self,
1750 crypto: &impl OpenMlsCrypto,
1751 ciphersuite: Ciphersuite,
1752 ) -> Result<PathGenerationSecret, VirtualClientsError> {
1753 let path_generation_secret =
1754 self.0
1755 .derive_secret(crypto, ciphersuite, PATH_GENERATION_LABEL)?;
1756 Ok(PathGenerationSecret(path_generation_secret))
1757 }
1758}
1759
1760#[derive(Debug)]
1769pub(crate) struct VcCommitMaterial {
1770 pub(crate) epoch_id: EpochId,
1772 pub(crate) operation_secret: OperationSecret,
1774 pub(crate) external_init_secret: Option<ExternalInitSecret>,
1776}
1777
1778#[derive(Debug, PartialEq, Eq)]
1802pub(crate) enum DerivationInfoTbe {
1803 LeafNode {
1808 leaf_index: LeafNodeIndex,
1809 generation: u32,
1810 external_init_secret: Option<ExternalInitSecret>,
1812 },
1813 KeyPackage {
1815 leaf_index: LeafNodeIndex,
1816 generation: u32,
1817 key_package_index: u32,
1818 },
1819}
1820
1821impl DerivationInfoTbe {
1822 pub(crate) fn leaf_index(&self) -> LeafNodeIndex {
1824 match self {
1825 Self::LeafNode { leaf_index, .. } | Self::KeyPackage { leaf_index, .. } => *leaf_index,
1826 }
1827 }
1828
1829 pub(crate) fn generation(&self) -> u32 {
1831 match self {
1832 Self::LeafNode { generation, .. } | Self::KeyPackage { generation, .. } => *generation,
1833 }
1834 }
1835
1836 pub(crate) fn external_init_secret(&self) -> Option<&ExternalInitSecret> {
1839 match self {
1840 Self::LeafNode {
1841 external_init_secret,
1842 ..
1843 } => external_init_secret.as_ref(),
1844 Self::KeyPackage { .. } => None,
1845 }
1846 }
1847
1848 fn tls_serialize_detached(&self) -> Result<Vec<u8>, tls_codec::Error> {
1852 match self {
1853 Self::LeafNode {
1854 leaf_index,
1855 generation,
1856 external_init_secret,
1857 } => {
1858 let mut out = Vec::with_capacity(
1859 leaf_index.tls_serialized_len()
1860 + generation.tls_serialized_len()
1861 + external_init_secret.tls_serialized_len(),
1862 );
1863 leaf_index.tls_serialize(&mut out)?;
1864 generation.tls_serialize(&mut out)?;
1865 external_init_secret.tls_serialize(&mut out)?;
1866 Ok(out)
1867 }
1868 Self::KeyPackage {
1869 leaf_index,
1870 generation,
1871 key_package_index,
1872 } => {
1873 let mut out = Vec::with_capacity(
1874 leaf_index.tls_serialized_len()
1875 + generation.tls_serialized_len()
1876 + key_package_index.tls_serialized_len(),
1877 );
1878 leaf_index.tls_serialize(&mut out)?;
1879 generation.tls_serialize(&mut out)?;
1880 key_package_index.tls_serialize(&mut out)?;
1881 Ok(out)
1882 }
1883 }
1884 }
1885
1886 fn deserialize_for_operation(
1892 bytes: &[u8],
1893 operation_type: VirtualClientOperationType,
1894 ) -> Result<Self, VirtualClientsError> {
1895 let (leaf_index, rest) = LeafNodeIndex::tls_deserialize_bytes(bytes)?;
1896 let (generation, rest) = u32::tls_deserialize_bytes(rest)?;
1897 let (tbe, rest) = match operation_type {
1898 VirtualClientOperationType::KeyPackage => {
1899 let (key_package_index, rest) = u32::tls_deserialize_bytes(rest)?;
1900 (
1901 Self::KeyPackage {
1902 leaf_index,
1903 generation,
1904 key_package_index,
1905 },
1906 rest,
1907 )
1908 }
1909 VirtualClientOperationType::LeafNode => {
1913 let (external_init_secret, rest) =
1914 Option::<ExternalInitSecret>::tls_deserialize_bytes(rest)?;
1915 (
1916 Self::LeafNode {
1917 leaf_index,
1918 generation,
1919 external_init_secret,
1920 },
1921 rest,
1922 )
1923 }
1924 VirtualClientOperationType::Application => {
1925 return Err(VirtualClientsError::DerivationInfoMalformed);
1926 }
1927 };
1928 if !rest.is_empty() {
1929 return Err(VirtualClientsError::DerivationInfoMalformed);
1930 }
1931 Ok(tbe)
1932 }
1933}
1934
1935#[openmls_traits::maybe_async]
1941pub(crate) async fn load_vc_epoch_state_and_tree<Provider: OpenMlsProvider>(
1942 provider: &Provider,
1943 epoch_id: &EpochId,
1944) -> Result<
1945 (
1946 VcDerivationEpochState,
1947 crate::components::vc_operation_tree::OperationSecretTree,
1948 ),
1949 VirtualClientsError,
1950> {
1951 use openmls_traits::storage::StorageProvider as _;
1952
1953 let storage = provider.storage();
1954 let state = storage
1955 .vc_derivation_epoch_state(epoch_id)
1956 .await
1957 .map_err(|e| {
1958 log::error!("vc: load derivation epoch state failed: {e:?}");
1959 VirtualClientsError::StorageError
1960 })?
1961 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
1962 let operation_tree = storage
1963 .vc_operation_tree(epoch_id)
1964 .await
1965 .map_err(|e| {
1966 log::error!("vc: load operation tree failed: {e:?}");
1967 VirtualClientsError::StorageError
1968 })?
1969 .ok_or(VirtualClientsError::MissingOperationTree)?;
1970 Ok((state, operation_tree))
1971}
1972
1973pub(crate) fn resolve_vc_leaf_dictionary(
1985 caller_capabilities: Option<&crate::treesync::node::leaf_node::Capabilities>,
1986 caller_extensions: Option<
1987 &crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
1988 >,
1989 current_leaf: Option<&crate::treesync::node::leaf_node::LeafNode>,
1990) -> Result<crate::extensions::AppDataDictionary, VirtualClientsError> {
1991 use crate::{
1992 component::{ComponentId, ComponentType},
1993 extensions::ExtensionType,
1994 };
1995 use tls_codec::DeserializeBytes as _;
1996
1997 let supports_app_data_dictionary = match caller_capabilities {
1998 Some(c) => c.extensions().contains(&ExtensionType::AppDataDictionary),
1999 None => current_leaf
2000 .map(|leaf| {
2001 leaf.capabilities()
2002 .extensions()
2003 .contains(&ExtensionType::AppDataDictionary)
2004 })
2005 .unwrap_or(false),
2006 };
2007 if !supports_app_data_dictionary {
2008 return Err(VirtualClientsError::AppDataDictionaryNotSupported);
2009 }
2010
2011 let mut resolved_dictionary = current_leaf
2012 .and_then(|leaf| leaf.extensions().app_data_dictionary())
2013 .map(|ext| ext.dictionary().clone())
2014 .unwrap_or_default();
2015 if let Some(caller_dict) = caller_extensions.and_then(|exts| exts.app_data_dictionary()) {
2016 for entry in caller_dict.dictionary().entries() {
2017 resolved_dictionary.insert(entry.id(), entry.data().to_vec());
2018 }
2019 }
2020
2021 let app_components_bytes = resolved_dictionary
2022 .get(&ComponentId::from(ComponentType::AppComponents))
2023 .map(<[u8]>::to_vec);
2024 let Some(app_components_bytes) = app_components_bytes else {
2025 return Err(VirtualClientsError::VcComponentNotListed);
2026 };
2027
2028 let supported_components = Vec::<u16>::tls_deserialize_exact_bytes(&app_components_bytes)
2031 .map_err(|e| {
2032 log::error!("vc: AppComponents body failed to deserialize: {e:?}");
2033 VirtualClientsError::VcComponentNotListed
2034 })?;
2035 if !supported_components.contains(&VC_COMPONENT_ID) {
2036 return Err(VirtualClientsError::VcComponentNotListed);
2037 }
2038
2039 Ok(resolved_dictionary)
2040}
2041
2042pub(crate) fn merge_vc_derivation_info(
2050 caller_extensions: Option<
2051 &crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
2052 >,
2053 mut resolved_dictionary: crate::extensions::AppDataDictionary,
2054 derivation_info_bytes: Vec<u8>,
2055) -> Result<
2056 crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
2057 crate::error::LibraryError,
2058> {
2059 use crate::extensions::{AppDataDictionaryExtension, Extension, Extensions};
2060
2061 resolved_dictionary.insert(VC_COMPONENT_ID, derivation_info_bytes);
2062 let vc_extension =
2063 Extension::AppDataDictionary(AppDataDictionaryExtension::new(resolved_dictionary));
2064
2065 let other_extensions = caller_extensions
2066 .map(|exts| {
2067 exts.iter()
2068 .filter(|ext| !matches!(ext, Extension::AppDataDictionary(_)))
2069 .cloned()
2070 .collect::<Vec<_>>()
2071 })
2072 .unwrap_or_default();
2073 let new_extensions: Vec<Extension> = other_extensions
2074 .into_iter()
2075 .chain(std::iter::once(vc_extension))
2076 .collect();
2077 Extensions::from_vec(new_extensions)
2078 .map_err(|_| crate::error::LibraryError::custom("Failed to build VC leaf-node extensions"))
2079}
2080
2081#[cfg(test)]
2082mod tests {
2083 use super::*;
2084 use openmls_rust_crypto::{MemoryStorage, OpenMlsRustCrypto};
2085 use openmls_traits::{
2086 random::OpenMlsRand,
2087 storage::{StorageProvider, CURRENT_VERSION},
2088 OpenMlsProvider,
2089 };
2090
2091 const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
2092
2093 fn register_epoch_state(provider: &OpenMlsRustCrypto, leaf_index: LeafNodeIndex) -> EpochId {
2097 use crate::components::vc_operation_tree::OperationSecretTree;
2098
2099 let emulator = EmulatorEpochSecret::new(
2100 &provider
2101 .rand()
2102 .random_vec(CIPHERSUITE.hash_length())
2103 .expect("randomness"),
2104 );
2105 let epoch_id = emulator
2106 .derive_epoch_id(provider.crypto(), CIPHERSUITE)
2107 .expect("derive epoch id");
2108 let epoch_encryption_key = emulator
2109 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
2110 .expect("derive epoch encryption key");
2111 let reuse_guard_secret = emulator
2112 .derive_reuse_guard_secret(provider.crypto(), CIPHERSUITE)
2113 .expect("derive reuse guard secret");
2114 let generation_id_secret = emulator
2115 .derive_generation_id_secret(provider.crypto(), CIPHERSUITE)
2116 .expect("derive generation id secret");
2117 let epoch_base_secret = emulator
2118 .derive_epoch_base_secret(provider.crypto(), CIPHERSUITE)
2119 .expect("derive epoch base secret");
2120 let emulation_group_size = TreeSize::new(2);
2121 let state = VcDerivationEpochState::new(
2122 leaf_index,
2123 epoch_encryption_key,
2124 reuse_guard_secret,
2125 generation_id_secret,
2126 emulation_group_size,
2127 CIPHERSUITE,
2128 );
2129 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_derivation_epoch_state(
2130 provider.storage(),
2131 &epoch_id,
2132 &state,
2133 )
2134 .expect("write derivation epoch state");
2135 let operation_tree = OperationSecretTree::new(epoch_base_secret, emulation_group_size);
2136 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_operation_tree(
2137 provider.storage(),
2138 &epoch_id,
2139 &operation_tree,
2140 )
2141 .expect("write operation tree");
2142 epoch_id
2143 }
2144
2145 #[test]
2148 fn assemble_upload_reads_leaf_index_from_state() {
2149 let provider = OpenMlsRustCrypto::default();
2150 let leaf_index = LeafNodeIndex::new(5);
2151 let epoch_id = register_epoch_state(&provider, leaf_index);
2152 let infos = vec![
2153 KeyPackageInfo {
2154 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2155 cipher_suite: CIPHERSUITE,
2156 key_package_index: 0,
2157 extensions: Extensions::empty(),
2158 },
2159 KeyPackageInfo {
2160 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2161 cipher_suite: CIPHERSUITE,
2162 key_package_index: 1,
2163 extensions: Extensions::empty(),
2164 },
2165 ];
2166
2167 let upload = assemble_vc_key_package_upload(provider.storage(), epoch_id.clone(), 4, infos)
2168 .expect("assemble upload");
2169
2170 assert_eq!(upload.epoch_id, epoch_id);
2171 assert_eq!(upload.leaf_index, leaf_index);
2172 assert_eq!(upload.generation, 4);
2173 assert_eq!(upload.key_package_info.len(), 2);
2174 }
2175
2176 #[test]
2179 fn assemble_upload_without_state_fails() {
2180 let provider = OpenMlsRustCrypto::default();
2181 let epoch_id = EpochId(b"unregistered-epoch".to_vec().into());
2182 let err = assemble_vc_key_package_upload(provider.storage(), epoch_id, 0, Vec::new())
2183 .expect_err("assemble must fail without registered state");
2184 assert_eq!(err, VirtualClientsError::MissingDerivationEpochState);
2185 }
2186
2187 #[test]
2191 fn process_upload_stores_records() {
2192 let provider = OpenMlsRustCrypto::default();
2193 let leaf_index = LeafNodeIndex::new(0);
2194 let epoch_id = register_epoch_state(&provider, leaf_index);
2195 let ref_a = KeyPackageRef::from_slice(b"kp-ref-a");
2196 let ref_b = KeyPackageRef::from_slice(b"kp-ref-b");
2197 let upload = KeyPackageUpload {
2198 epoch_id: epoch_id.clone(),
2199 leaf_index,
2200 generation: 0,
2201 key_package_info: vec![
2202 KeyPackageInfo {
2203 key_package_ref: ref_a.clone(),
2204 cipher_suite: CIPHERSUITE,
2205 key_package_index: 0,
2206 extensions: Extensions::empty(),
2207 },
2208 KeyPackageInfo {
2209 key_package_ref: ref_b.clone(),
2210 cipher_suite: CIPHERSUITE,
2211 key_package_index: 1,
2212 extensions: Extensions::empty(),
2213 },
2214 ],
2215 };
2216
2217 process_vc_key_package_upload(&provider, &upload).expect("process upload");
2218
2219 let material_a: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2220 CURRENT_VERSION,
2221 >>::retained_key_package_material(
2222 provider.storage(), &ref_a
2223 )
2224 .expect("read material a")
2225 .expect("material a present");
2226 assert_eq!(material_a.epoch_id, epoch_id);
2227 assert_eq!(material_a.leaf_index, leaf_index);
2228 assert_eq!(material_a.generation, 0);
2229 assert_eq!(material_a.key_package_index, 0);
2230 assert_eq!(material_a.key_package_ciphersuite, CIPHERSUITE);
2231
2232 let material_b: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2233 CURRENT_VERSION,
2234 >>::retained_key_package_material(
2235 provider.storage(), &ref_b
2236 )
2237 .expect("read material b")
2238 .expect("material b present");
2239 assert_eq!(material_b.epoch_id, epoch_id);
2240 assert_eq!(material_b.leaf_index, leaf_index);
2241 assert_eq!(material_b.generation, 0);
2242 assert_eq!(material_b.key_package_index, 1);
2243 assert_eq!(material_b.key_package_ciphersuite, CIPHERSUITE);
2244 }
2245
2246 #[test]
2247 fn retained_material_without_extensions_deserializes_as_last_resort() {
2248 let provider = OpenMlsRustCrypto::default();
2249 let leaf_index = LeafNodeIndex::new(0);
2250 let epoch_id = register_epoch_state(&provider, leaf_index);
2251 let key_package_ref = KeyPackageRef::from_slice(b"kp-ref");
2252 let upload = KeyPackageUpload {
2253 epoch_id,
2254 leaf_index,
2255 generation: 0,
2256 key_package_info: vec![KeyPackageInfo {
2257 key_package_ref: key_package_ref.clone(),
2258 cipher_suite: CIPHERSUITE,
2259 key_package_index: 0,
2260 extensions: Extensions::empty(),
2261 }],
2262 };
2263 process_vc_key_package_upload(&provider, &upload).expect("process upload");
2264 let material: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2265 CURRENT_VERSION,
2266 >>::retained_key_package_material(
2267 provider.storage(), &key_package_ref
2268 )
2269 .expect("read material")
2270 .expect("material present");
2271
2272 let mut record = serde_json::to_value(&material).expect("serialize material");
2273 record
2274 .as_object_mut()
2275 .expect("material serializes as an object")
2276 .remove("key_package_extensions")
2277 .expect("record has the extensions field");
2278 let material: RetainedKeyPackageMaterial =
2279 serde_json::from_value(record).expect("deserialize record without extensions");
2280
2281 assert_eq!(material.key_package_extensions, last_resort_extensions());
2282 assert_eq!(material.key_package_index, 0);
2283 }
2284
2285 #[test]
2287 fn delete_key_package_removes_vc_record() {
2288 let provider = OpenMlsRustCrypto::default();
2289 let leaf_index = LeafNodeIndex::new(0);
2290 let epoch_id = register_epoch_state(&provider, leaf_index);
2291 let kp_ref = KeyPackageRef::from_slice(b"kp-ref");
2292 let upload = KeyPackageUpload {
2293 epoch_id,
2294 leaf_index,
2295 generation: 0,
2296 key_package_info: vec![KeyPackageInfo {
2297 key_package_ref: kp_ref.clone(),
2298 cipher_suite: CIPHERSUITE,
2299 key_package_index: 0,
2300 extensions: Extensions::empty(),
2301 }],
2302 };
2303 process_vc_key_package_upload(&provider, &upload).expect("process upload");
2304
2305 let present: Option<RetainedKeyPackageMaterial> = <MemoryStorage as StorageProvider<
2306 CURRENT_VERSION,
2307 >>::retained_key_package_material(
2308 provider.storage(), &kp_ref
2309 )
2310 .expect("read material");
2311 assert!(present.is_some());
2312
2313 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::delete_key_package(
2314 provider.storage(),
2315 &kp_ref,
2316 )
2317 .expect("delete key package");
2318
2319 let after: Option<RetainedKeyPackageMaterial> = <MemoryStorage as StorageProvider<
2320 CURRENT_VERSION,
2321 >>::retained_key_package_material(
2322 provider.storage(), &kp_ref
2323 )
2324 .expect("read material after delete");
2325 assert!(after.is_none());
2326 }
2327
2328 fn setup_key_and_epoch_id(provider: &OpenMlsRustCrypto) -> (EpochEncryptionKey, EpochId) {
2329 let emulator = EmulatorEpochSecret::new(
2330 &provider
2331 .rand()
2332 .random_vec(CIPHERSUITE.hash_length())
2333 .expect("randomness"),
2334 );
2335 let key = emulator
2336 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
2337 .expect("derive ek");
2338 let epoch_id = emulator
2339 .derive_epoch_id(provider.crypto(), CIPHERSUITE)
2340 .expect("derive epoch id");
2341 (key, epoch_id)
2342 }
2343
2344 #[test]
2350 fn derivation_info_tbe_roundtrip() {
2351 let provider = OpenMlsRustCrypto::default();
2352 let (key, epoch_id) = setup_key_and_epoch_id(&provider);
2353 let leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
2354
2355 let key_package_tbe = DerivationInfoTbe::KeyPackage {
2356 leaf_index: LeafNodeIndex::new(7),
2357 generation: 3,
2358 key_package_index: 5,
2359 };
2360 let leaf_node_tbe = DerivationInfoTbe::LeafNode {
2361 leaf_index: LeafNodeIndex::new(7),
2362 generation: 3,
2363 external_init_secret: None,
2364 };
2365 let external_commit_tbe = DerivationInfoTbe::LeafNode {
2366 leaf_index: LeafNodeIndex::new(7),
2367 generation: 3,
2368 external_init_secret: Some(ExternalInitSecret::from_slice(b"external init secret")),
2369 };
2370
2371 let key_package_bytes = key_package_tbe
2375 .tls_serialize_detached()
2376 .expect("serialize key package tbe");
2377 let leaf_node_bytes = leaf_node_tbe
2378 .tls_serialize_detached()
2379 .expect("serialize leaf node tbe");
2380 assert_eq!(key_package_bytes.len(), leaf_node_bytes.len() + 3);
2381
2382 for (original, operation_type) in [
2383 (key_package_tbe, VirtualClientOperationType::KeyPackage),
2384 (leaf_node_tbe, VirtualClientOperationType::LeafNode),
2385 (external_commit_tbe, VirtualClientOperationType::LeafNode),
2386 ] {
2387 let derivation_info = DerivationInfo::encrypt(
2388 provider.crypto(),
2389 CIPHERSUITE,
2390 &key,
2391 epoch_id.clone(),
2392 &leaf_encryption_key,
2393 &original,
2394 )
2395 .expect("encrypt");
2396 assert_eq!(derivation_info.epoch_id(), &epoch_id);
2397 let decrypted = derivation_info
2398 .decrypt(
2399 provider.crypto(),
2400 CIPHERSUITE,
2401 &key,
2402 &leaf_encryption_key,
2403 operation_type,
2404 )
2405 .expect("decrypt");
2406 assert_eq!(original, decrypted);
2407 }
2408 }
2409
2410 #[test]
2416 fn derivation_info_tbe_wire_format_matches_spec() {
2417 let absent = DerivationInfoTbe::LeafNode {
2418 leaf_index: LeafNodeIndex::new(7),
2419 generation: 3,
2420 external_init_secret: None,
2421 }
2422 .tls_serialize_detached()
2423 .expect("serialize");
2424 assert_eq!(
2425 absent,
2426 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00]
2427 );
2428
2429 let present = DerivationInfoTbe::LeafNode {
2430 leaf_index: LeafNodeIndex::new(7),
2431 generation: 3,
2432 external_init_secret: Some(ExternalInitSecret::from_slice(b"init")),
2433 }
2434 .tls_serialize_detached()
2435 .expect("serialize");
2436 assert_eq!(
2437 present,
2438 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x01, 0x04, b'i', b'n', b'i', b't']
2439 );
2440
2441 let key_package = DerivationInfoTbe::KeyPackage {
2442 leaf_index: LeafNodeIndex::new(7),
2443 generation: 3,
2444 key_package_index: 5,
2445 }
2446 .tls_serialize_detached()
2447 .expect("serialize");
2448 assert_eq!(
2449 key_package,
2450 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05]
2451 );
2452 }
2453
2454 #[test]
2459 fn derivation_info_tbe_rejects_trailing_data() {
2460 let variants = [
2461 (
2462 DerivationInfoTbe::LeafNode {
2463 leaf_index: LeafNodeIndex::new(7),
2464 generation: 3,
2465 external_init_secret: None,
2466 },
2467 VirtualClientOperationType::LeafNode,
2468 ),
2469 (
2470 DerivationInfoTbe::KeyPackage {
2471 leaf_index: LeafNodeIndex::new(7),
2472 generation: 3,
2473 key_package_index: 5,
2474 },
2475 VirtualClientOperationType::KeyPackage,
2476 ),
2477 ];
2478 for (tbe, operation_type) in variants {
2479 let mut bytes = tbe.tls_serialize_detached().expect("serialize");
2480 bytes.push(0x00);
2481 let result = DerivationInfoTbe::deserialize_for_operation(&bytes, operation_type);
2482 assert_eq!(result, Err(VirtualClientsError::DerivationInfoMalformed));
2483 }
2484 }
2485
2486 #[test]
2488 fn external_init_secret_debug_is_redacted() {
2489 let tbe = DerivationInfoTbe::LeafNode {
2490 leaf_index: LeafNodeIndex::new(7),
2491 generation: 3,
2492 external_init_secret: Some(ExternalInitSecret::from_slice(b"very secret bytes")),
2493 };
2494 let debug = format!("{tbe:?}");
2495 assert!(debug.contains("<redacted>"));
2496 assert!(!debug.contains("secret bytes"));
2497 assert!(!debug.to_lowercase().contains("76657279"));
2498 }
2499
2500 #[test]
2505 fn decryption_fails_with_wrong_leaf_encryption_key() {
2506 let provider = OpenMlsRustCrypto::default();
2507 let (key, epoch_id) = setup_key_and_epoch_id(&provider);
2508 let leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
2509 let tbe = DerivationInfoTbe::LeafNode {
2510 leaf_index: LeafNodeIndex::new(1),
2511 generation: 0,
2512 external_init_secret: None,
2513 };
2514 let derivation_info = DerivationInfo::encrypt(
2515 provider.crypto(),
2516 CIPHERSUITE,
2517 &key,
2518 epoch_id,
2519 &leaf_encryption_key,
2520 &tbe,
2521 )
2522 .expect("encrypt");
2523 let other_leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
2524 let err = derivation_info
2525 .decrypt(
2526 provider.crypto(),
2527 CIPHERSUITE,
2528 &key,
2529 &other_leaf_encryption_key,
2530 VirtualClientOperationType::LeafNode,
2531 )
2532 .expect_err("decryption with the wrong context must fail");
2533 assert_eq!(err, VirtualClientsError::DerivationInfoDecryptionFailed);
2534 }
2535
2536 #[test]
2540 fn key_package_seed_derivation_is_indexed_and_label_separated() {
2541 let provider = OpenMlsRustCrypto::default();
2542 let operation_secret = OperationSecret::from(Secret::from_slice(
2543 &provider
2544 .rand()
2545 .random_vec(CIPHERSUITE.hash_length())
2546 .expect("randomness"),
2547 ));
2548
2549 let seed_zero = operation_secret
2550 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2551 .expect("derive seed 0");
2552 let seed_zero_again = operation_secret
2553 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2554 .expect("derive seed 0 again");
2555 let seed_one = operation_secret
2556 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 1)
2557 .expect("derive seed 1");
2558
2559 let init_zero = seed_zero
2560 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2561 .expect("derive init key 0")
2562 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2563 .expect("generate init pair 0");
2564 let init_zero_again = seed_zero_again
2565 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2566 .expect("derive init key 0 again")
2567 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2568 .expect("generate init pair 0 again");
2569 let init_one = seed_one
2570 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2571 .expect("derive init key 1")
2572 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2573 .expect("generate init pair 1");
2574
2575 assert_eq!(init_zero.public, init_zero_again.public);
2577 assert_ne!(init_zero.public, init_one.public);
2579
2580 let encryption_zero = seed_zero
2582 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2583 .expect("derive encryption key 0")
2584 .generate_encryption_key_pair(provider.crypto(), CIPHERSUITE)
2585 .expect("generate encryption pair 0");
2586 assert_ne!(
2587 init_zero.public.as_slice(),
2588 encryption_zero.public_key().as_slice()
2589 );
2590 }
2591
2592 #[test]
2597 fn key_package_seed_binds_target_ciphersuite() {
2598 let provider = OpenMlsRustCrypto::default();
2599 let operation_secret = OperationSecret::from(Secret::from_slice(
2600 &provider
2601 .rand()
2602 .random_vec(CIPHERSUITE.hash_length())
2603 .expect("randomness"),
2604 ));
2605 let other_ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519;
2608
2609 let seed = operation_secret
2610 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2611 .expect("derive seed");
2612 let seed_other_suite = operation_secret
2613 .derive_key_package_seed_secret(provider.crypto(), other_ciphersuite, 0)
2614 .expect("derive seed under other target ciphersuite");
2615
2616 assert_ne!(seed.0.as_slice(), seed_other_suite.0.as_slice());
2617 }
2618
2619 #[test]
2624 fn target_operation_secret_binds_ciphersuite_and_group_id() {
2625 let provider = OpenMlsRustCrypto::default();
2626 let operation_secret = OperationSecret::from(Secret::from_slice(
2627 &provider
2628 .rand()
2629 .random_vec(CIPHERSUITE.hash_length())
2630 .expect("randomness"),
2631 ));
2632 let group_id = GroupId::from_slice(b"group-a");
2633 let other_ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519;
2634
2635 let target = operation_secret
2636 .derive_target_operation_secret(provider.crypto(), CIPHERSUITE, &group_id)
2637 .expect("derive target operation secret");
2638 let target_again = operation_secret
2639 .derive_target_operation_secret(provider.crypto(), CIPHERSUITE, &group_id)
2640 .expect("derive target operation secret again");
2641 let target_other_group = operation_secret
2642 .derive_target_operation_secret(
2643 provider.crypto(),
2644 CIPHERSUITE,
2645 &GroupId::from_slice(b"group-b"),
2646 )
2647 .expect("derive target operation secret for other group");
2648 let target_other_suite = operation_secret
2649 .derive_target_operation_secret(provider.crypto(), other_ciphersuite, &group_id)
2650 .expect("derive target operation secret under other target ciphersuite");
2651
2652 assert_eq!(target.0.as_slice(), target_again.0.as_slice());
2654 assert_ne!(target.0.as_slice(), target_other_group.0.as_slice());
2657 assert_ne!(target.0.as_slice(), target_other_suite.0.as_slice());
2658
2659 let encryption_key_secret = target
2662 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2663 .expect("derive encryption key secret");
2664 let path_generation_secret = target
2665 .derive_path_generation_secret(provider.crypto(), CIPHERSUITE)
2666 .expect("derive path generation secret");
2667 assert_ne!(
2668 encryption_key_secret.0.as_slice(),
2669 path_generation_secret.0.as_slice()
2670 );
2671 }
2672
2673 #[test]
2677 fn group_creation_secret_derivation_is_deterministic_and_label_separated() {
2678 let provider = OpenMlsRustCrypto::default();
2679 let operation_secret = OperationSecret::from(Secret::from_slice(
2680 &provider
2681 .rand()
2682 .random_vec(CIPHERSUITE.hash_length())
2683 .expect("randomness"),
2684 ));
2685
2686 let seed_zero = operation_secret
2687 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2688 .expect("derive seed 0");
2689 let seed_one = operation_secret
2690 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 1)
2691 .expect("derive seed 1");
2692
2693 let epoch_secret_zero = seed_zero
2694 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2695 .expect("derive group creation secret 0");
2696 let epoch_secret_zero_again = seed_zero
2697 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2698 .expect("derive group creation secret 0 again");
2699 let epoch_secret_one = seed_one
2700 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2701 .expect("derive group creation secret 1");
2702
2703 assert_eq!(
2705 epoch_secret_zero.as_slice(),
2706 epoch_secret_zero_again.as_slice()
2707 );
2708 assert_ne!(epoch_secret_zero.as_slice(), epoch_secret_one.as_slice());
2710
2711 let encryption_key_secret = seed_zero
2714 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2715 .expect("derive encryption key 0");
2716 assert_ne!(
2717 epoch_secret_zero.as_slice(),
2718 encryption_key_secret.0.as_slice()
2719 );
2720 }
2721
2722 #[test]
2725 fn validate_rejects_duplicate_index() {
2726 let infos = vec![
2727 KeyPackageInfo {
2728 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2729 cipher_suite: CIPHERSUITE,
2730 key_package_index: 2,
2731 extensions: Extensions::empty(),
2732 },
2733 KeyPackageInfo {
2734 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2735 cipher_suite: CIPHERSUITE,
2736 key_package_index: 2,
2737 extensions: Extensions::empty(),
2738 },
2739 ];
2740 let err = validate_key_package_infos(&infos).expect_err("duplicate index must be rejected");
2741 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageIndex(2));
2742 }
2743
2744 #[test]
2746 fn validate_rejects_duplicate_ref() {
2747 let infos = vec![
2748 KeyPackageInfo {
2749 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2750 cipher_suite: CIPHERSUITE,
2751 key_package_index: 0,
2752 extensions: Extensions::empty(),
2753 },
2754 KeyPackageInfo {
2755 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2756 cipher_suite: CIPHERSUITE,
2757 key_package_index: 1,
2758 extensions: Extensions::empty(),
2759 },
2760 ];
2761 let err = validate_key_package_infos(&infos).expect_err("duplicate ref must be rejected");
2762 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageRef);
2763 }
2764
2765 #[test]
2767 fn validate_accepts_distinct_infos() {
2768 let infos = vec![
2769 KeyPackageInfo {
2770 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2771 cipher_suite: CIPHERSUITE,
2772 key_package_index: 0,
2773 extensions: Extensions::empty(),
2774 },
2775 KeyPackageInfo {
2776 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2777 cipher_suite: CIPHERSUITE,
2778 key_package_index: 1,
2779 extensions: Extensions::empty(),
2780 },
2781 ];
2782 validate_key_package_infos(&infos).expect("distinct infos must pass");
2783 }
2784
2785 #[test]
2789 fn process_upload_rejects_malformed_without_consuming_generation() {
2790 let provider = OpenMlsRustCrypto::default();
2791 let leaf_index = LeafNodeIndex::new(0);
2792 let epoch_id = register_epoch_state(&provider, leaf_index);
2793 let ref_a = KeyPackageRef::from_slice(b"kp-ref-a");
2794 let ref_b = KeyPackageRef::from_slice(b"kp-ref-b");
2795
2796 let malformed = KeyPackageUpload {
2797 epoch_id: epoch_id.clone(),
2798 leaf_index,
2799 generation: 0,
2800 key_package_info: vec![
2801 KeyPackageInfo {
2802 key_package_ref: ref_a.clone(),
2803 cipher_suite: CIPHERSUITE,
2804 key_package_index: 0,
2805 extensions: Extensions::empty(),
2806 },
2807 KeyPackageInfo {
2808 key_package_ref: ref_b.clone(),
2809 cipher_suite: CIPHERSUITE,
2810 key_package_index: 0,
2811 extensions: Extensions::empty(),
2812 },
2813 ],
2814 };
2815 let err = process_vc_key_package_upload(&provider, &malformed)
2816 .expect_err("malformed upload must be rejected");
2817 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageIndex(0));
2818
2819 let valid = KeyPackageUpload {
2820 epoch_id: epoch_id.clone(),
2821 leaf_index,
2822 generation: 0,
2823 key_package_info: vec![
2824 KeyPackageInfo {
2825 key_package_ref: ref_a.clone(),
2826 cipher_suite: CIPHERSUITE,
2827 key_package_index: 0,
2828 extensions: Extensions::empty(),
2829 },
2830 KeyPackageInfo {
2831 key_package_ref: ref_b.clone(),
2832 cipher_suite: CIPHERSUITE,
2833 key_package_index: 1,
2834 extensions: Extensions::empty(),
2835 },
2836 ],
2837 };
2838 process_vc_key_package_upload(&provider, &valid)
2839 .expect("valid upload reusing the same generation must succeed");
2840
2841 let material_a: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2842 CURRENT_VERSION,
2843 >>::retained_key_package_material(
2844 provider.storage(), &ref_a
2845 )
2846 .expect("read material a")
2847 .expect("material a present");
2848 assert_eq!(material_a.epoch_id, epoch_id);
2849 assert_eq!(material_a.generation, 0);
2850 assert_eq!(material_a.key_package_index, 0);
2851
2852 let material_b: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2853 CURRENT_VERSION,
2854 >>::retained_key_package_material(
2855 provider.storage(), &ref_b
2856 )
2857 .expect("read material b")
2858 .expect("material b present");
2859 assert_eq!(material_b.key_package_index, 1);
2860 }
2861
2862 fn state_from_secret_bytes(
2865 provider: &OpenMlsRustCrypto,
2866 secret_bytes: &[u8],
2867 leaf_index: LeafNodeIndex,
2868 ) -> VcDerivationEpochState {
2869 let emulator = EmulatorEpochSecret::new(secret_bytes);
2870 let epoch_encryption_key = emulator
2871 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
2872 .expect("derive epoch encryption key");
2873 let reuse_guard_secret = emulator
2874 .derive_reuse_guard_secret(provider.crypto(), CIPHERSUITE)
2875 .expect("derive reuse guard secret");
2876 let generation_id_secret = emulator
2877 .derive_generation_id_secret(provider.crypto(), CIPHERSUITE)
2878 .expect("derive generation id secret");
2879 VcDerivationEpochState::new(
2880 leaf_index,
2881 epoch_encryption_key,
2882 reuse_guard_secret,
2883 generation_id_secret,
2884 TreeSize::new(2),
2885 CIPHERSUITE,
2886 )
2887 }
2888
2889 #[test]
2894 fn generation_id_is_deterministic_and_context_sensitive() {
2895 let provider = OpenMlsRustCrypto::default();
2896 let secret_bytes = provider
2897 .rand()
2898 .random_vec(CIPHERSUITE.hash_length())
2899 .expect("randomness");
2900 let state = state_from_secret_bytes(&provider, &secret_bytes, LeafNodeIndex::new(0));
2901
2902 let group_id = GroupId::from_slice(b"higher-level-group");
2903 let epoch = GroupEpoch::from(7);
2904 let derive = |group_id: &GroupId, epoch, generation, ratchet_type| {
2905 state
2906 .derive_generation_id(provider.crypto(), group_id, epoch, generation, ratchet_type)
2907 .expect("derive generation id")
2908 };
2909
2910 let base = derive(&group_id, epoch, 3, RatchetType::Application);
2911 assert_eq!(base.as_slice().len(), CIPHERSUITE.hash_length());
2913 assert_eq!(base, derive(&group_id, epoch, 3, RatchetType::Application));
2915 assert_ne!(base, derive(&group_id, epoch, 4, RatchetType::Application));
2918 assert_ne!(
2919 base,
2920 derive(&group_id, GroupEpoch::from(8), 3, RatchetType::Application)
2921 );
2922 assert_ne!(
2923 base,
2924 derive(
2925 &GroupId::from_slice(b"other-group"),
2926 epoch,
2927 3,
2928 RatchetType::Application
2929 )
2930 );
2931 assert_ne!(base, derive(&group_id, epoch, 3, RatchetType::Handshake));
2932
2933 let sibling = state_from_secret_bytes(&provider, &secret_bytes, LeafNodeIndex::new(5));
2937 let sibling_id = sibling
2938 .derive_generation_id(
2939 provider.crypto(),
2940 &group_id,
2941 epoch,
2942 3,
2943 RatchetType::Application,
2944 )
2945 .expect("sibling derive generation id");
2946 assert_eq!(base, sibling_id);
2947 }
2948
2949 #[test]
2950 #[allow(deprecated)]
2951 fn legacy_registration_record_layout_is_frozen() {
2952 let record = RegisteredVcDerivationEpoch {
2953 group_epoch: GroupEpoch::from(7),
2954 epoch_id: EpochId::new(vec![1, 2, 3]),
2955 };
2956 let json = serde_json::to_string(&record).expect("serialize legacy record");
2957 assert_eq!(json, r#"{"group_epoch":7,"epoch_id":[1,2,3]}"#);
2958 let decoded: RegisteredVcDerivationEpoch =
2959 serde_json::from_str(&json).expect("deserialize legacy record");
2960 assert_eq!(decoded, record);
2961 }
2962
2963 #[test]
2964 #[allow(deprecated)]
2965 fn legacy_bindings_record_layout_is_frozen() {
2966 let record = VcEmulationBindings {
2967 bindings: VecDeque::from([
2968 (GroupEpoch::from(7), EpochId::new(vec![1, 2, 3])),
2969 (GroupEpoch::from(8), EpochId::new(vec![4, 5, 6])),
2970 ]),
2971 };
2972 let json = serde_json::to_string(&record).expect("serialize legacy record");
2973 assert_eq!(json, r#"{"bindings":[[7,[1,2,3]],[8,[4,5,6]]]}"#);
2974 let decoded: VcEmulationBindings =
2975 serde_json::from_str(&json).expect("deserialize legacy record");
2976 assert_eq!(decoded, record);
2977
2978 let entries = decoded.into_entries();
2979 assert_eq!(
2980 entries,
2981 vec![
2982 (GroupEpoch::from(7), EpochId::new(vec![1, 2, 3])),
2983 (GroupEpoch::from(8), EpochId::new(vec![4, 5, 6])),
2984 ]
2985 );
2986 }
2987
2988 #[test]
2989 fn log_entry_layout_is_frozen() {
2990 let entry = VcDerivationEpochLogEntry {
2991 sequence: 2,
2992 group_epoch: GroupEpoch::from(7),
2993 epoch_id: EpochId::new(vec![1, 2, 3]),
2994 registered_at: SystemTime::UNIX_EPOCH + std::time::Duration::new(1_700_000_000, 42),
2995 };
2996 let json = serde_json::to_string(&entry).expect("serialize log entry");
2997 assert_eq!(
2998 json,
2999 r#"{"sequence":2,"group_epoch":7,"epoch_id":[1,2,3],"registered_at":{"secs_since_epoch":1700000000,"nanos_since_epoch":42}}"#
3000 );
3001 let decoded: VcDerivationEpochLogEntry =
3002 serde_json::from_str(&json).expect("deserialize log entry");
3003 assert_eq!(decoded, entry);
3004 }
3005
3006 #[test]
3007 fn binding_layout_is_frozen() {
3008 let binding = VcEmulationBinding {
3009 group_epoch: GroupEpoch::from(7),
3010 epoch_id: EpochId::new(vec![1, 2, 3]),
3011 };
3012 let json = serde_json::to_string(&binding).expect("serialize binding");
3013 assert_eq!(json, r#"{"group_epoch":7,"epoch_id":[1,2,3]}"#);
3014 let decoded: VcEmulationBinding = serde_json::from_str(&json).expect("deserialize binding");
3015 assert_eq!(decoded, binding);
3016 }
3017
3018 #[test]
3019 fn binding_from_legacy_record() {
3020 let epoch_id = EpochId::new(vec![4, 5, 6]);
3021 let binding = VcEmulationBinding::from_legacy_record(GroupEpoch::from(3), epoch_id.clone());
3022 assert_eq!(binding.group_epoch, GroupEpoch::from(3));
3023 assert_eq!(binding.epoch_id(), &epoch_id);
3024 }
3025
3026 #[test]
3027 fn log_entry_from_legacy_record() {
3028 let epoch_id = EpochId::new(vec![4, 5, 6]);
3029 let registered_at = SystemTime::UNIX_EPOCH;
3030 let entry = VcDerivationEpochLogEntry::from_legacy_record(
3031 GroupEpoch::from(3),
3032 epoch_id.clone(),
3033 registered_at,
3034 );
3035
3036 assert_eq!(entry.sequence, 0);
3037 assert_eq!(entry.group_epoch, GroupEpoch::from(3));
3038 assert_eq!(entry.epoch_id(), &epoch_id);
3039 assert_eq!(entry.registered_at, registered_at);
3040
3041 let mut log = VcDerivationEpochLog {
3044 entries: VecDeque::from([entry]),
3045 };
3046 assert!(log.shrink_to(1).is_empty());
3047 assert!(log.drop_superseded_before(SystemTime::now()).is_empty());
3048 assert_eq!(
3049 log.newest().map(|entry| entry.epoch_id.clone()),
3050 Some(epoch_id)
3051 );
3052 }
3053
3054 fn log_entry(
3055 group_epoch: u64,
3056 epoch_id: &EpochId,
3057 registered_at: SystemTime,
3058 ) -> VcDerivationEpochLogEntry {
3059 VcDerivationEpochLogEntry {
3060 sequence: group_epoch,
3061 group_epoch: GroupEpoch::from(group_epoch),
3062 epoch_id: epoch_id.clone(),
3063 registered_at,
3064 }
3065 }
3066
3067 #[test]
3068 fn log_reconstruction_orders_by_sequence() {
3069 let provider = OpenMlsRustCrypto::default();
3070 let group_id = GroupId::from_slice(b"emulation-group");
3071 let first = log_entry(0, &EpochId::new(vec![1]), SystemTime::UNIX_EPOCH);
3072 let second = log_entry(1, &EpochId::new(vec![2]), SystemTime::UNIX_EPOCH);
3073 let third = log_entry(2, &EpochId::new(vec![3]), SystemTime::UNIX_EPOCH);
3074 for entry in [&second, &third, &first] {
3077 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_derivation_epoch_log_entry(
3078 provider.storage(),
3079 &group_id,
3080 &entry.epoch_id,
3081 entry,
3082 )
3083 .expect("write log entry");
3084 }
3085
3086 let log = VcDerivationEpochLog::load(provider.storage(), &group_id).expect("load the log");
3087 assert_eq!(
3088 log.newest().map(|entry| entry.epoch_id.clone()),
3089 Some(third.epoch_id.clone())
3090 );
3091 assert_eq!(
3092 newest_vc_derivation_epoch(provider.storage(), &group_id).expect("newest epoch"),
3093 Some(third.epoch_id.clone())
3094 );
3095
3096 let mut log = log;
3098 assert_eq!(log.shrink_to(2), vec![first.epoch_id.clone()]);
3099 assert_eq!(
3100 log.newest().map(|entry| entry.epoch_id.clone()),
3101 Some(third.epoch_id)
3102 );
3103 }
3104
3105 #[test]
3106 fn wall_clock_sweep_measures_from_supersession() {
3107 let old = EpochId::new(vec![1]);
3108 let mid = EpochId::new(vec![2]);
3109 let new = EpochId::new(vec![3]);
3110 let start = SystemTime::UNIX_EPOCH;
3111 let minutes = |m: u64| std::time::Duration::from_secs(m * 60);
3112 let mut log = VcDerivationEpochLog {
3115 entries: VecDeque::from([
3116 log_entry(0, &old, start),
3117 log_entry(1, &mid, start + minutes(23 * 60 + 50)),
3118 log_entry(2, &new, start + minutes(24 * 60)),
3119 ]),
3120 };
3121
3122 assert!(log.drop_superseded_before(start + minutes(5)).is_empty());
3125 assert_eq!(
3128 log.drop_superseded_before(start + minutes(23 * 60 + 55)),
3129 vec![old]
3130 );
3131 assert_eq!(
3133 log.drop_superseded_before(start + minutes(48 * 60)),
3134 vec![mid]
3135 );
3136 assert_eq!(log.newest().map(|entry| entry.epoch_id.clone()), Some(new));
3137 }
3138}