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 group::{
24 mls_group::errors::RegisterVcDerivationEpochError, GroupEpoch, GroupId,
25 VcDerivationEpochRetentionPolicy,
26 },
27 key_packages::InitKey,
28 messages::PathSecret,
29 schedule::application_export_tree::{ApplicationExportTree, ApplicationExportTreeError},
30 treesync::node::encryption_keys::EncryptionKeyPair,
31};
32
33pub const VC_COMPONENT_ID: u16 = 0x667A;
39
40const ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
47const PATH_GENERATION_LABEL: &str = "Path Generation";
48const INIT_KEY_LABEL: &str = "Init Key";
49const KEY_PACKAGE_SEED_LABEL: &str = "vc key package seed";
55const TARGET_OPERATION_LABEL: &str = "vc target operation";
58const GROUP_CREATION_LABEL: &str = "Group Creation";
61
62const DERIVATION_INFO_KEY_LABEL: &str = "key";
65const DERIVATION_INFO_NONCE_LABEL: &str = "nonce";
68
69const EPOCH_ID_LABEL: &str = "Epoch ID";
70const EPOCH_ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
71const EPOCH_BASE_SECRET_LABEL: &str = "Base Secret";
72const REUSE_GUARD_LABEL: &str = "Reuse Guard";
74const GENERATION_ID_LABEL: &str = "Generation ID Secret";
76const GENERATION_ID_EXPAND_LABEL: &str = "generation id";
80const REUSE_GUARD_PRP_KEY_LABEL: &str = "reuse guard";
83const PRP_KEY_LEN: usize = 16;
85
86#[derive(Error, Debug, PartialEq, Clone)]
88pub enum VirtualClientsError {
89 #[error("Failed to deserialize derivation info.")]
91 DerivationInfoMalformed,
92 #[error("Failed to decrypt derivation info.")]
95 DerivationInfoDecryptionFailed,
96 #[error("No virtual-clients operation secret tree for this epoch.")]
99 MissingOperationTree,
100 #[error("No virtual-clients derivation-epoch state for this epoch.")]
103 MissingDerivationEpochState,
104 #[error("No derivation epoch is registered for the group.")]
108 NoDerivationEpoch,
109 #[error("Virtual-clients storage error")]
112 StorageError,
113 #[error("Leaf encryption key from path does not match the derived key.")]
116 EncryptionKeyMismatch,
117 #[error("Cryptographic operation failed.")]
119 CryptoError(#[from] CryptoError),
120 #[error(
122 "Hash function produced output of length {actual_length}, expected {expected_length}."
123 )]
124 HashOutputLengthMismatch {
125 actual_length: usize,
127 expected_length: usize,
129 },
130 #[error("TLS codec error: {0}")]
134 Tls(#[from] tls_codec::Error),
135 #[error("Leaf does not declare AppDataDictionary support in its capabilities.")]
138 AppDataDictionaryNotSupported,
139 #[error("Leaf's AppComponents entry does not list the virtual-clients component id.")]
143 VcComponentNotListed,
144 #[error("Leaf index is outside the operation secret tree.")]
146 IndexOutOfBounds,
147 #[error("The operation coordinates name the caller's own leaf index.")]
150 OwnLeafIndex,
151 #[error("The operation secret for this generation was already consumed.")]
154 OperationGenerationConsumed,
155 #[error("The requested operation generation is too far beyond the ratchet head.")]
159 OperationGenerationTooDistant,
160 #[error("Operation ratchet generation has reached `u32::MAX`.")]
162 OperationRatchetTooLong,
163 #[error("An unrecoverable error has occurred due to a bug in the implementation.")]
166 LibraryError,
167 #[error("KeyPackageUpload contains a duplicate key_package_index: {0}.")]
170 DuplicateKeyPackageIndex(u32),
171 #[error("KeyPackageUpload contains a duplicate KeyPackageRef.")]
174 DuplicateKeyPackageRef,
175}
176
177#[derive(Debug, Serialize, Deserialize)]
181pub(crate) struct EmulatorEpochSecret(Secret);
182
183impl EmulatorEpochSecret {
184 pub(crate) fn new(bytes: &[u8]) -> Self {
188 Self(Secret::from_slice(bytes))
189 }
190
191 pub(crate) fn derive_epoch_id(
192 &self,
193 crypto: &impl OpenMlsCrypto,
194 ciphersuite: Ciphersuite,
195 ) -> Result<EpochId, VirtualClientsError> {
196 let secret = self.0.derive_secret(crypto, ciphersuite, EPOCH_ID_LABEL)?;
197 Ok(EpochId(secret.as_slice().to_vec().into()))
198 }
199
200 pub(crate) fn derive_epoch_encryption_key(
204 &self,
205 crypto: &impl OpenMlsCrypto,
206 ciphersuite: Ciphersuite,
207 ) -> Result<EpochEncryptionKey, VirtualClientsError> {
208 let secret = self
209 .0
210 .derive_secret(crypto, ciphersuite, EPOCH_ENCRYPTION_KEY_LABEL)?;
211 Ok(EpochEncryptionKey(secret))
212 }
213
214 pub(crate) fn derive_epoch_base_secret(
215 &self,
216 crypto: &impl OpenMlsCrypto,
217 ciphersuite: Ciphersuite,
218 ) -> Result<Secret, VirtualClientsError> {
219 Ok(self
220 .0
221 .derive_secret(crypto, ciphersuite, EPOCH_BASE_SECRET_LABEL)?)
222 }
223
224 pub(crate) fn derive_reuse_guard_secret(
226 &self,
227 crypto: &impl OpenMlsCrypto,
228 ciphersuite: Ciphersuite,
229 ) -> Result<ReuseGuardSecret, VirtualClientsError> {
230 let secret = self
231 .0
232 .derive_secret(crypto, ciphersuite, REUSE_GUARD_LABEL)?;
233 Ok(ReuseGuardSecret(secret))
234 }
235
236 pub(crate) fn derive_generation_id_secret(
238 &self,
239 crypto: &impl OpenMlsCrypto,
240 ciphersuite: Ciphersuite,
241 ) -> Result<GenerationIdSecret, VirtualClientsError> {
242 let secret = self
243 .0
244 .derive_secret(crypto, ciphersuite, GENERATION_ID_LABEL)?;
245 Ok(GenerationIdSecret(secret))
246 }
247}
248
249#[derive(Debug, Serialize, Deserialize)]
253pub(crate) struct ReuseGuardSecret(Secret);
254
255impl ReuseGuardSecret {
256 #[cfg(test)]
258 pub(crate) fn from_secret_for_tests(secret: Secret) -> Self {
259 Self(secret)
260 }
261
262 pub(crate) fn derive_prp_key(
272 &self,
273 crypto: &impl OpenMlsCrypto,
274 ciphersuite: Ciphersuite,
275 key_schedule_nonce: &[u8],
276 ) -> Result<[u8; PRP_KEY_LEN], VirtualClientsError> {
277 let key = self.0.kdf_expand_label(
278 crypto,
279 ciphersuite,
280 REUSE_GUARD_PRP_KEY_LABEL,
281 key_schedule_nonce,
282 PRP_KEY_LEN,
283 )?;
284 key.as_slice()
285 .try_into()
286 .map_err(|_| VirtualClientsError::HashOutputLengthMismatch {
287 actual_length: key.as_slice().len(),
288 expected_length: PRP_KEY_LEN,
289 })
290 }
291}
292
293#[derive(Debug, Serialize, Deserialize)]
298pub(crate) struct GenerationIdSecret(Secret);
299
300impl GenerationIdSecret {
301 fn derive_generation_id(
312 &self,
313 crypto: &impl OpenMlsCrypto,
314 ciphersuite: Ciphersuite,
315 context: &PrivateMessageContext<'_>,
316 ) -> Result<GenerationId, VirtualClientsError> {
317 let context_bytes = context.tls_serialize_detached()?;
318 let generation_id = self.0.kdf_expand_label(
319 crypto,
320 ciphersuite,
321 GENERATION_ID_EXPAND_LABEL,
322 &context_bytes,
323 ciphersuite.hash_length(),
324 )?;
325 Ok(GenerationId(generation_id.as_slice().to_vec().into()))
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, TlsSize, TlsSerialize)]
346#[repr(u8)]
347pub enum RatchetType {
348 Application = 1,
350 Handshake = 2,
352}
353
354#[derive(Debug, TlsSize, TlsSerialize)]
371pub(crate) struct PrivateMessageContext<'a> {
372 group_id: VLByteSlice<'a>,
373 epoch: u64,
374 generation: u32,
375 ratchet_type: RatchetType,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
390pub struct GenerationId(VLByteVec);
391
392impl GenerationId {
393 pub fn as_slice(&self) -> &[u8] {
395 self.0.as_slice()
396 }
397}
398
399#[derive(Debug, TlsSize, TlsSerialize, TlsDeserializeBytes)]
415pub(crate) struct DerivationInfo {
416 epoch_id: EpochId,
417 ciphertext: VLByteVec,
418}
419
420impl DerivationInfo {
421 pub(crate) fn encrypt(
426 crypto: &impl OpenMlsCrypto,
427 ciphersuite: Ciphersuite,
428 key: &EpochEncryptionKey,
429 epoch_id: EpochId,
430 leaf_encryption_key: &[u8],
431 tbe: &DerivationInfoTbe,
432 ) -> Result<Self, VirtualClientsError> {
433 let (aead_key, aead_nonce) =
434 key.derive_key_nonce(crypto, ciphersuite, leaf_encryption_key)?;
435 let payload = tbe.tls_serialize_detached()?;
436 let ciphertext = crypto.aead_encrypt(
437 ciphersuite.aead_algorithm(),
438 aead_key.as_slice(),
439 payload.as_slice(),
440 aead_nonce.as_slice(),
441 epoch_id.0.as_slice(),
442 )?;
443 Ok(Self {
444 epoch_id,
445 ciphertext: ciphertext.into(),
446 })
447 }
448
449 pub(crate) fn epoch_id(&self) -> &EpochId {
450 &self.epoch_id
451 }
452
453 pub(crate) fn decrypt(
457 &self,
458 crypto: &impl OpenMlsCrypto,
459 ciphersuite: Ciphersuite,
460 key: &EpochEncryptionKey,
461 leaf_encryption_key: &[u8],
462 operation_type: VirtualClientOperationType,
463 ) -> Result<DerivationInfoTbe, VirtualClientsError> {
464 let (aead_key, aead_nonce) =
465 key.derive_key_nonce(crypto, ciphersuite, leaf_encryption_key)?;
466 let plaintext = crypto
467 .aead_decrypt(
468 ciphersuite.aead_algorithm(),
469 aead_key.as_slice(),
470 self.ciphertext.as_slice(),
471 aead_nonce.as_slice(),
472 self.epoch_id.0.as_slice(),
473 )
474 .map_err(|e| {
475 log::error!("vc: aead decrypt derivation info failed: {e:?}");
476 VirtualClientsError::DerivationInfoDecryptionFailed
477 })?;
478 DerivationInfoTbe::deserialize_for_operation(&plaintext, operation_type)
479 }
480}
481
482#[derive(
487 Debug,
488 Clone,
489 PartialEq,
490 Eq,
491 PartialOrd,
492 Ord,
493 Serialize,
494 Deserialize,
495 TlsSize,
496 TlsSerialize,
497 TlsDeserializeBytes,
498)]
499pub struct EpochId(VLByteVec);
500
501impl EpochId {
502 pub fn new(bytes: Vec<u8>) -> Self {
504 Self(bytes.into())
505 }
506
507 pub fn as_bytes(&self) -> &[u8] {
509 self.0.as_slice()
510 }
511}
512
513#[derive(Debug, PartialEq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
533pub struct KeyPackageInfo {
534 pub key_package_ref: KeyPackageRef,
536 pub cipher_suite: Ciphersuite,
538 pub key_package_index: u32,
540}
541
542#[derive(Debug, PartialEq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
565pub struct KeyPackageUpload {
566 pub epoch_id: EpochId,
568 pub leaf_index: LeafNodeIndex,
570 pub generation: u32,
572 pub key_package_info: Vec<KeyPackageInfo>,
574}
575
576#[derive(Debug, Serialize, Deserialize)]
589pub struct RetainedKeyPackageMaterial {
590 pub epoch_id: EpochId,
592 pub leaf_index: LeafNodeIndex,
595 pub generation: u32,
597 pub key_package_ciphersuite: Ciphersuite,
599 pub key_package_index: u32,
601 pub key_package_seed_secret: KeyPackageSeedSecret,
604}
605
606fn validate_key_package_infos(infos: &[KeyPackageInfo]) -> Result<(), VirtualClientsError> {
616 let mut seen_indices = BTreeSet::new();
617 let mut seen_refs = BTreeSet::new();
618 for info in infos {
619 if !seen_indices.insert(info.key_package_index) {
620 return Err(VirtualClientsError::DuplicateKeyPackageIndex(
621 info.key_package_index,
622 ));
623 }
624 if !seen_refs.insert(&info.key_package_ref) {
625 return Err(VirtualClientsError::DuplicateKeyPackageRef);
626 }
627 }
628 Ok(())
629}
630
631pub fn assemble_vc_key_package_upload<Storage: crate::storage::StorageProvider>(
650 storage: &Storage,
651 epoch_id: EpochId,
652 generation: u32,
653 key_package_info: Vec<KeyPackageInfo>,
654) -> Result<KeyPackageUpload, VirtualClientsError> {
655 validate_key_package_infos(&key_package_info)?;
656 let state: VcDerivationEpochState = storage
657 .vc_derivation_epoch_state(&epoch_id)
658 .map_err(|e| {
659 log::error!("vc: load derivation epoch state in assemble upload failed: {e:?}");
660 VirtualClientsError::StorageError
661 })?
662 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
663 Ok(KeyPackageUpload {
664 epoch_id,
665 leaf_index: state.leaf_index,
666 generation,
667 key_package_info,
668 })
669}
670
671pub fn process_vc_key_package_upload<Provider: OpenMlsProvider>(
686 provider: &Provider,
687 upload: &KeyPackageUpload,
688) -> Result<(), VirtualClientsError> {
689 use crate::components::vc_operation_tree::OperationSecretTree;
690 use openmls_traits::storage::StorageProvider as _;
691
692 validate_key_package_infos(&upload.key_package_info)?;
693
694 let storage = provider.storage();
695 let crypto = provider.crypto();
696
697 let state: VcDerivationEpochState = storage
698 .vc_derivation_epoch_state(&upload.epoch_id)
699 .map_err(|e| {
700 log::error!("vc: load derivation epoch state in process upload failed: {e:?}");
701 VirtualClientsError::StorageError
702 })?
703 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
704 let mut operation_tree: OperationSecretTree = storage
705 .vc_operation_tree(&upload.epoch_id)
706 .map_err(|e| {
707 log::error!("vc: load operation tree in process upload failed: {e:?}");
708 VirtualClientsError::StorageError
709 })?
710 .ok_or(VirtualClientsError::MissingOperationTree)?;
711 let emulation_ciphersuite = state.emulation_ciphersuite;
712
713 let operation_secret = operation_tree.derive_operation_secret(
715 crypto,
716 emulation_ciphersuite,
717 &upload.epoch_id,
718 upload.leaf_index,
719 VirtualClientOperationType::KeyPackage,
720 upload.generation,
721 b"",
722 )?;
723
724 let mut materials = Vec::with_capacity(upload.key_package_info.len());
725 for info in &upload.key_package_info {
726 let key_package_seed_secret = operation_secret.derive_key_package_seed_secret(
727 crypto,
728 info.cipher_suite,
729 info.key_package_index,
730 )?;
731 let material = RetainedKeyPackageMaterial {
732 epoch_id: upload.epoch_id.clone(),
733 leaf_index: upload.leaf_index,
734 generation: upload.generation,
735 key_package_ciphersuite: info.cipher_suite,
736 key_package_index: info.key_package_index,
737 key_package_seed_secret,
738 };
739 materials.push((info.key_package_ref.clone(), material));
740 }
741
742 storage
743 .write_retained_key_package_material_batch(&upload.epoch_id, &operation_tree, &materials)
744 .map_err(|e| {
745 log::error!("vc: persist batch key package material in process upload failed: {e:?}");
746 VirtualClientsError::StorageError
747 })?;
748 Ok(())
749}
750
751#[derive(Debug)]
762pub(crate) struct VcWelcomeMaterial {
763 pub(crate) key_package_ref: KeyPackageRef,
765 pub(crate) epoch_id: EpochId,
767 pub(crate) leaf_index: LeafNodeIndex,
770 pub(crate) generation: u32,
772 pub(crate) key_package_index: u32,
774 pub(crate) init_private_key: openmls_traits::types::HpkePrivateKey,
777 pub(crate) init_key: InitKey,
779 pub(crate) encryption_keypair: EncryptionKeyPair,
782}
783
784#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
787pub struct VcDerivationEpochLogEntry {
788 pub(crate) sequence: u64,
790 pub(crate) group_epoch: GroupEpoch,
792 pub(crate) epoch_id: EpochId,
794 pub(crate) registered_at: SystemTime,
796}
797
798impl VcDerivationEpochLogEntry {
799 pub fn from_legacy_record(
802 group_epoch: GroupEpoch,
803 epoch_id: EpochId,
804 registered_at: SystemTime,
805 ) -> Self {
806 Self {
807 sequence: 0,
808 group_epoch,
809 epoch_id,
810 registered_at,
811 }
812 }
813
814 pub fn epoch_id(&self) -> &EpochId {
816 &self.epoch_id
817 }
818}
819
820#[derive(Debug, Default)]
825pub(crate) struct VcDerivationEpochLog {
826 entries: VecDeque<VcDerivationEpochLogEntry>,
828}
829
830impl VcDerivationEpochLog {
831 pub(crate) fn load<Storage: crate::storage::StorageProvider>(
834 storage: &Storage,
835 group_id: &GroupId,
836 ) -> Result<Self, Storage::Error> {
837 let mut entries: Vec<VcDerivationEpochLogEntry> =
838 storage.vc_derivation_epoch_log_entries(group_id)?;
839 entries.sort_unstable_by_key(|entry| entry.sequence);
840 debug_assert!(
844 entries
845 .iter()
846 .map(|entry| &entry.epoch_id)
847 .collect::<BTreeSet<_>>()
848 .len()
849 == entries.len(),
850 "duplicate derivation epoch id in log"
851 );
852 Ok(Self {
853 entries: entries.into(),
854 })
855 }
856
857 pub(crate) fn is_empty(&self) -> bool {
858 self.entries.is_empty()
859 }
860
861 pub(crate) fn newest(&self) -> Option<&VcDerivationEpochLogEntry> {
863 self.entries.back()
864 }
865
866 fn push(&mut self, group_epoch: GroupEpoch, epoch_id: EpochId) -> VcDerivationEpochLogEntry {
870 let sequence = self
871 .entries
872 .back()
873 .map_or(0, |entry| entry.sequence.saturating_add(1));
874 let entry = VcDerivationEpochLogEntry {
875 sequence,
876 group_epoch,
877 epoch_id,
878 registered_at: SystemTime::now(),
879 };
880 self.entries.push_back(entry.clone());
881 entry
882 }
883
884 pub(crate) fn shrink_to(&mut self, max_entries: usize) -> Vec<EpochId> {
888 let excess = self.entries.len().saturating_sub(max_entries.max(1));
889 self.drop_oldest(excess)
890 }
891
892 pub(crate) fn drop_superseded_before(&mut self, cutoff: SystemTime) -> Vec<EpochId> {
897 let count = self
898 .entries
899 .iter()
900 .skip(1)
901 .rposition(|successor| successor.registered_at < cutoff)
902 .map_or(0, |index| index + 1);
903 self.drop_oldest(count)
904 }
905
906 fn drop_oldest(&mut self, count: usize) -> Vec<EpochId> {
909 let droppable = self.entries.len().saturating_sub(1);
910 self.entries
911 .drain(0..count.min(droppable))
912 .map(|entry| entry.epoch_id)
913 .collect()
914 }
915}
916
917#[deprecated(
921 since = "0.9.0",
922 note = "migration-only: decode pre-log registration records and convert them with \
923 `VcDerivationEpochLogEntry::from_legacy_record`. Will be removed in 0.10.0."
924)]
925#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
926pub struct RegisteredVcDerivationEpoch {
927 pub group_epoch: GroupEpoch,
929 pub epoch_id: EpochId,
931}
932
933pub(crate) fn newest_vc_derivation_epoch<Storage: crate::storage::StorageProvider>(
939 storage: &Storage,
940 emulation_group_id: &GroupId,
941) -> Result<Option<EpochId>, Storage::Error> {
942 let entries: Vec<VcDerivationEpochLogEntry> =
943 storage.vc_derivation_epoch_log_entries(emulation_group_id)?;
944 Ok(entries
945 .into_iter()
946 .max_by_key(|entry| entry.sequence)
947 .map(|entry| entry.epoch_id))
948}
949
950pub(crate) fn require_newest_vc_derivation_epoch<Storage: crate::storage::StorageProvider>(
963 storage: &Storage,
964 emulation_group_id: &GroupId,
965) -> Result<EpochId, VirtualClientsError> {
966 newest_vc_derivation_epoch(storage, emulation_group_id)
967 .map_err(|e| {
968 log::error!("vc: load newest derivation epoch for a new operation failed: {e:?}");
969 VirtualClientsError::StorageError
970 })?
971 .ok_or(VirtualClientsError::NoDerivationEpoch)
972}
973
974pub(crate) struct VcDerivationEpochParams<'a> {
978 pub(crate) group_id: &'a GroupId,
980 pub(crate) ciphersuite: Ciphersuite,
982 pub(crate) group_epoch: GroupEpoch,
984 pub(crate) own_leaf_index: LeafNodeIndex,
986 pub(crate) tree_size: TreeSize,
988 pub(crate) retention_policy: VcDerivationEpochRetentionPolicy,
990}
991
992impl<'a> VcDerivationEpochParams<'a> {
993 pub(crate) fn for_public_group(
1000 public_group: &'a crate::group::PublicGroup,
1001 own_leaf_index: LeafNodeIndex,
1002 retention_policy: VcDerivationEpochRetentionPolicy,
1003 ) -> Self {
1004 Self {
1005 group_id: public_group.group_id(),
1006 ciphersuite: public_group.ciphersuite(),
1007 group_epoch: public_group.group_context().epoch(),
1008 own_leaf_index,
1009 tree_size: public_group.tree_size(),
1010 retention_policy,
1011 }
1012 }
1013}
1014
1015pub(crate) fn register_vc_derivation_epoch<
1043 Crypto: OpenMlsCrypto,
1044 Storage: crate::storage::StorageProvider,
1045>(
1046 crypto: &Crypto,
1047 storage: &Storage,
1048 export_tree: Option<&mut ApplicationExportTree>,
1049 params: VcDerivationEpochParams<'_>,
1050) -> Result<EpochId, RegisterVcDerivationEpochError<Storage::Error>> {
1051 let VcDerivationEpochParams {
1052 group_id,
1053 ciphersuite,
1054 group_epoch,
1055 own_leaf_index,
1056 tree_size,
1057 retention_policy,
1058 } = params;
1059 let export_tree =
1060 export_tree.ok_or(RegisterVcDerivationEpochError::MissingApplicationExportTree)?;
1061
1062 let mut log = VcDerivationEpochLog::load(storage, group_id).map_err(|e| {
1063 log::error!("vc: load derivation epoch log before registration failed: {e:?}");
1064 RegisterVcDerivationEpochError::Storage(e)
1065 })?;
1066
1067 let bytes = match export_tree.safe_export_secret(crypto, ciphersuite, VC_COMPONENT_ID) {
1072 Ok(bytes) => bytes,
1073 Err(ApplicationExportTreeError::PuncturedInput) => {
1074 if let Some(newest) = log.newest() {
1077 if newest.group_epoch == group_epoch {
1078 return Ok(newest.epoch_id.clone());
1079 }
1080 }
1081 return Err(RegisterVcDerivationEpochError::ApplicationExportTree(
1082 ApplicationExportTreeError::PuncturedInput,
1083 ));
1084 }
1085 Err(e) => return Err(e.into()),
1086 };
1087 let emulator_epoch_secret = EmulatorEpochSecret::new(bytes.as_slice());
1088 let epoch_id = emulator_epoch_secret.derive_epoch_id(crypto, ciphersuite)?;
1089 if let Some(newest) = log.newest() {
1090 if newest.group_epoch == group_epoch && newest.epoch_id == epoch_id {
1091 return Ok(newest.epoch_id.clone());
1096 }
1097 }
1098 let epoch_encryption_key =
1099 emulator_epoch_secret.derive_epoch_encryption_key(crypto, ciphersuite)?;
1100 let epoch_base_secret = emulator_epoch_secret.derive_epoch_base_secret(crypto, ciphersuite)?;
1101 let reuse_guard_secret =
1102 emulator_epoch_secret.derive_reuse_guard_secret(crypto, ciphersuite)?;
1103 let generation_id_secret =
1104 emulator_epoch_secret.derive_generation_id_secret(crypto, ciphersuite)?;
1105 let operation_tree = OperationSecretTree::new(epoch_base_secret, tree_size);
1106 let state = VcDerivationEpochState::new(
1107 own_leaf_index,
1108 epoch_encryption_key,
1109 reuse_guard_secret,
1110 generation_id_secret,
1111 tree_size,
1112 ciphersuite,
1113 );
1114 let entry = log.push(group_epoch, epoch_id.clone());
1115 let dropped = log.shrink_to(retention_policy.max_epochs().unwrap_or(usize::MAX));
1116
1117 storage
1118 .write_vc_operation_tree(&epoch_id, &operation_tree)
1119 .map_err(|e| {
1120 log::error!("vc: persist operation tree at registration failed: {e:?}");
1121 RegisterVcDerivationEpochError::Storage(e)
1122 })?;
1123 storage
1124 .write_vc_derivation_epoch_state(&epoch_id, &state)
1125 .map_err(|e| {
1126 log::error!("vc: persist derivation epoch state at registration failed: {e:?}");
1127 RegisterVcDerivationEpochError::Storage(e)
1128 })?;
1129 storage
1130 .write_vc_derivation_epoch_log_entry(group_id, &epoch_id, &entry)
1131 .map_err(|e| {
1132 log::error!("vc: persist derivation epoch log entry at registration failed: {e:?}");
1133 RegisterVcDerivationEpochError::Storage(e)
1134 })?;
1135 if !dropped.is_empty() {
1136 storage
1137 .delete_vc_derivation_epoch_log_entries(group_id, &dropped)
1138 .map_err(|e| {
1139 log::error!("vc: prune derivation epoch log at registration failed: {e:?}");
1140 RegisterVcDerivationEpochError::Storage(e)
1141 })?;
1142 }
1143 storage
1147 .delete_unreferenced_vc_derivation_epoch_states::<EpochId>()
1148 .map_err(|e| {
1149 log::error!("vc: release pruned derivation epochs at registration failed: {e:?}");
1150 RegisterVcDerivationEpochError::Storage(e)
1151 })?;
1152
1153 Ok(epoch_id)
1154}
1155
1156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1165pub struct VcEmulationBinding {
1166 pub(crate) group_epoch: GroupEpoch,
1168 pub(crate) epoch_id: EpochId,
1170}
1171
1172impl VcEmulationBinding {
1173 pub fn from_legacy_record(group_epoch: GroupEpoch, epoch_id: EpochId) -> Self {
1176 Self {
1177 group_epoch,
1178 epoch_id,
1179 }
1180 }
1181
1182 pub fn epoch_id(&self) -> &EpochId {
1184 &self.epoch_id
1185 }
1186
1187 pub(crate) fn into_epoch_id(self) -> EpochId {
1188 self.epoch_id
1189 }
1190}
1191
1192#[deprecated(
1196 since = "0.9.0",
1197 note = "migration-only: decode pre-row bindings records and convert their entries with \
1198 `VcEmulationBinding::from_legacy_record`. Will be removed in 0.10.0."
1199)]
1200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1201pub struct VcEmulationBindings {
1202 bindings: VecDeque<(GroupEpoch, EpochId)>,
1204}
1205
1206#[allow(deprecated)]
1207impl VcEmulationBindings {
1208 pub fn into_entries(self) -> Vec<(GroupEpoch, EpochId)> {
1210 self.bindings.into()
1211 }
1212}
1213
1214pub(crate) fn write_vc_emulation_binding_with_pruning<Storage: crate::storage::StorageProvider>(
1220 storage: &Storage,
1221 group_id: &GroupId,
1222 group_epoch: GroupEpoch,
1223 epoch_id: EpochId,
1224 max_entries: usize,
1225) -> Result<(), Storage::Error> {
1226 let binding = VcEmulationBinding {
1227 group_epoch,
1228 epoch_id: epoch_id.clone(),
1229 };
1230 storage.write_vc_emulation_binding(group_id, &group_epoch, &epoch_id, &binding)?;
1231 let mut bindings: Vec<VcEmulationBinding> = storage.vc_emulation_bindings(group_id)?;
1232 if bindings.len() > max_entries {
1233 bindings.sort_unstable_by_key(|binding| binding.group_epoch.as_u64());
1234 let stale: Vec<GroupEpoch> = bindings[..bindings.len() - max_entries]
1235 .iter()
1236 .map(|binding| binding.group_epoch)
1237 .collect();
1238 storage.delete_vc_emulation_bindings(group_id, &stale)?;
1239 }
1240 Ok(())
1241}
1242
1243#[derive(Debug, Serialize, Deserialize)]
1260pub(crate) struct EpochEncryptionKey(Secret);
1261
1262impl EpochEncryptionKey {
1263 fn derive_key_nonce(
1267 &self,
1268 crypto: &impl OpenMlsCrypto,
1269 ciphersuite: Ciphersuite,
1270 leaf_encryption_key: &[u8],
1271 ) -> Result<(Secret, Secret), VirtualClientsError> {
1272 let key = self.0.kdf_expand_label(
1273 crypto,
1274 ciphersuite,
1275 DERIVATION_INFO_KEY_LABEL,
1276 leaf_encryption_key,
1277 ciphersuite.aead_key_length(),
1278 )?;
1279 let nonce = self.0.kdf_expand_label(
1280 crypto,
1281 ciphersuite,
1282 DERIVATION_INFO_NONCE_LABEL,
1283 leaf_encryption_key,
1284 ciphersuite.aead_nonce_length(),
1285 )?;
1286 Ok((key, nonce))
1287 }
1288}
1289
1290#[derive(Debug, Serialize, Deserialize)]
1301pub struct VcDerivationEpochState {
1302 pub(crate) leaf_index: LeafNodeIndex,
1306 pub(crate) epoch_encryption_key: EpochEncryptionKey,
1307 pub(crate) reuse_guard_secret: ReuseGuardSecret,
1308 pub(crate) generation_id_secret: GenerationIdSecret,
1311 pub(crate) emulation_group_size: TreeSize,
1313 pub(crate) emulation_ciphersuite: Ciphersuite,
1316}
1317
1318impl VcDerivationEpochState {
1319 pub(crate) fn new(
1320 leaf_index: LeafNodeIndex,
1321 epoch_encryption_key: EpochEncryptionKey,
1322 reuse_guard_secret: ReuseGuardSecret,
1323 generation_id_secret: GenerationIdSecret,
1324 emulation_group_size: TreeSize,
1325 emulation_ciphersuite: Ciphersuite,
1326 ) -> Self {
1327 Self {
1328 leaf_index,
1329 epoch_encryption_key,
1330 reuse_guard_secret,
1331 generation_id_secret,
1332 emulation_group_size,
1333 emulation_ciphersuite,
1334 }
1335 }
1336
1337 pub(crate) fn into_parts(self) -> (LeafNodeIndex, EpochEncryptionKey, Ciphersuite) {
1340 (
1341 self.leaf_index,
1342 self.epoch_encryption_key,
1343 self.emulation_ciphersuite,
1344 )
1345 }
1346
1347 pub(crate) fn derive_generation_id(
1353 &self,
1354 crypto: &impl OpenMlsCrypto,
1355 group_id: &GroupId,
1356 epoch: GroupEpoch,
1357 generation: u32,
1358 ratchet_type: RatchetType,
1359 ) -> Result<GenerationId, VirtualClientsError> {
1360 let context = PrivateMessageContext {
1361 group_id: VLByteSlice(group_id.as_slice()),
1362 epoch: epoch.as_u64(),
1363 generation,
1364 ratchet_type,
1365 };
1366 self.generation_id_secret
1367 .derive_generation_id(crypto, self.emulation_ciphersuite, &context)
1368 }
1369
1370 pub(crate) fn reuse_guard_inputs(&self) -> crate::framing::EmulatorReuseGuardCtx<'_> {
1373 crate::framing::EmulatorReuseGuardCtx {
1374 reuse_guard_secret: &self.reuse_guard_secret,
1375 emulation_ciphersuite: self.emulation_ciphersuite,
1376 emulation_group_size: self.emulation_group_size,
1377 emulation_leaf_index: self.leaf_index,
1378 }
1379 }
1380}
1381
1382#[derive(Debug, Serialize, Deserialize)]
1390pub struct OperationSecret(Secret);
1391
1392impl From<Secret> for OperationSecret {
1393 fn from(secret: Secret) -> Self {
1394 Self(secret)
1395 }
1396}
1397
1398fn import_secret(
1402 crypto: &impl OpenMlsCrypto,
1403 target_ciphersuite: Ciphersuite,
1404 source_secret: &Secret,
1405 label: &str,
1406 context: &[u8],
1407) -> Result<Secret, CryptoError> {
1408 let salt = Secret::from_slice(&[]);
1409 let target_prk = salt.hkdf_extract(crypto, target_ciphersuite, source_secret)?;
1410 target_prk.kdf_expand_label(
1411 crypto,
1412 target_ciphersuite,
1413 label,
1414 context,
1415 target_ciphersuite.hash_length(),
1416 )
1417}
1418
1419impl OperationSecret {
1420 pub(crate) fn as_slice(&self) -> &[u8] {
1422 self.0.as_slice()
1423 }
1424
1425 pub(crate) fn derive_target_operation_secret(
1441 &self,
1442 crypto: &impl OpenMlsCrypto,
1443 target_ciphersuite: Ciphersuite,
1444 group_id: &GroupId,
1445 ) -> Result<TargetOperationSecret, VirtualClientsError> {
1446 let context = TargetOperationContext {
1447 cipher_suite: target_ciphersuite,
1448 group_id: VLByteSlice(group_id.as_slice()),
1449 }
1450 .tls_serialize_detached()?;
1451 let secret = import_secret(
1452 crypto,
1453 target_ciphersuite,
1454 &self.0,
1455 TARGET_OPERATION_LABEL,
1456 &context,
1457 )?;
1458 Ok(TargetOperationSecret(secret))
1459 }
1460
1461 pub(crate) fn derive_key_package_seed_secret(
1475 &self,
1476 crypto: &impl OpenMlsCrypto,
1477 target_ciphersuite: Ciphersuite,
1478 key_package_index: u32,
1479 ) -> Result<KeyPackageSeedSecret, VirtualClientsError> {
1480 let context = KeyPackageSeedContext {
1481 cipher_suite: target_ciphersuite,
1482 key_package_index,
1483 }
1484 .tls_serialize_detached()?;
1485 let seed = import_secret(
1486 crypto,
1487 target_ciphersuite,
1488 &self.0,
1489 KEY_PACKAGE_SEED_LABEL,
1490 &context,
1491 )?;
1492 Ok(KeyPackageSeedSecret(seed))
1493 }
1494}
1495
1496#[derive(Debug, TlsSize, TlsSerialize)]
1509struct KeyPackageSeedContext {
1510 cipher_suite: Ciphersuite,
1511 key_package_index: u32,
1512}
1513
1514#[derive(Debug, Serialize, Deserialize)]
1521pub struct KeyPackageSeedSecret(Secret);
1522
1523impl KeyPackageSeedSecret {
1524 pub(crate) fn derive_init_key_secret(
1525 &self,
1526 crypto: &impl OpenMlsCrypto,
1527 ciphersuite: Ciphersuite,
1528 ) -> Result<InitKeySecret, VirtualClientsError> {
1529 let init_key_secret = self.0.derive_secret(crypto, ciphersuite, INIT_KEY_LABEL)?;
1530 Ok(InitKeySecret(init_key_secret))
1531 }
1532
1533 pub(crate) fn derive_encryption_key_secret(
1534 &self,
1535 crypto: &impl OpenMlsCrypto,
1536 ciphersuite: Ciphersuite,
1537 ) -> Result<EncryptionKeySecret, VirtualClientsError> {
1538 let encryption_key_secret =
1539 self.0
1540 .derive_secret(crypto, ciphersuite, ENCRYPTION_KEY_LABEL)?;
1541 Ok(EncryptionKeySecret(encryption_key_secret))
1542 }
1543
1544 pub(crate) fn derive_group_creation_secret(
1555 &self,
1556 crypto: &impl OpenMlsCrypto,
1557 ciphersuite: Ciphersuite,
1558 ) -> Result<Secret, VirtualClientsError> {
1559 Ok(self
1560 .0
1561 .derive_secret(crypto, ciphersuite, GROUP_CREATION_LABEL)?)
1562 }
1563}
1564
1565pub(crate) struct EncryptionKeySecret(Secret);
1566
1567impl EncryptionKeySecret {
1568 pub(crate) fn generate_encryption_key_pair(
1569 &self,
1570 crypto: &impl OpenMlsCrypto,
1571 ciphersuite: Ciphersuite,
1572 ) -> Result<EncryptionKeyPair, VirtualClientsError> {
1573 let hpke_config = ciphersuite.hpke_config();
1574 let key_pair = crypto.derive_hpke_keypair(hpke_config, self.0.as_slice())?;
1575 Ok(EncryptionKeyPair::from(key_pair))
1576 }
1577}
1578
1579pub(crate) struct InitKeySecret(Secret);
1580
1581impl InitKeySecret {
1582 pub(crate) fn generate_init_key_pair(
1583 &self,
1584 crypto: &impl OpenMlsCrypto,
1585 ciphersuite: Ciphersuite,
1586 ) -> Result<openmls_traits::types::HpkeKeyPair, VirtualClientsError> {
1587 let hpke_config = ciphersuite.hpke_config();
1588 let key_pair = crypto.derive_hpke_keypair(hpke_config, self.0.as_slice())?;
1589 Ok(key_pair)
1590 }
1591}
1592
1593pub(crate) struct PathGenerationSecret(Secret);
1594
1595impl From<PathGenerationSecret> for PathSecret {
1596 fn from(value: PathGenerationSecret) -> Self {
1597 value.0.into()
1598 }
1599}
1600
1601#[derive(Debug, Clone, Copy, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
1618#[repr(u8)]
1619pub enum VirtualClientOperationType {
1620 KeyPackage = 1,
1622 LeafNode = 2,
1625 Application = 3,
1627}
1628
1629#[derive(Clone, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
1642pub(crate) struct ExternalInitSecret(SecretVLByteVec);
1643
1644impl std::fmt::Debug for ExternalInitSecret {
1645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1646 f.debug_struct("ExternalInitSecret")
1647 .field("init_secret", &"<redacted>")
1648 .finish()
1649 }
1650}
1651
1652impl ExternalInitSecret {
1653 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1654 Self(bytes.to_vec().into())
1655 }
1656
1657 pub(crate) fn as_slice(&self) -> &[u8] {
1658 self.0.as_slice()
1659 }
1660}
1661
1662#[derive(Debug, TlsSize, TlsSerialize)]
1669struct TargetOperationContext<'a> {
1670 cipher_suite: Ciphersuite,
1671 group_id: VLByteSlice<'a>,
1672}
1673
1674#[derive(Debug)]
1678pub(crate) struct TargetOperationSecret(Secret);
1679
1680impl TargetOperationSecret {
1681 pub(crate) fn derive_encryption_key_secret(
1682 &self,
1683 crypto: &impl OpenMlsCrypto,
1684 ciphersuite: Ciphersuite,
1685 ) -> Result<EncryptionKeySecret, VirtualClientsError> {
1686 let encryption_key_secret =
1687 self.0
1688 .derive_secret(crypto, ciphersuite, ENCRYPTION_KEY_LABEL)?;
1689 Ok(EncryptionKeySecret(encryption_key_secret))
1690 }
1691
1692 pub(crate) fn derive_path_generation_secret(
1693 &self,
1694 crypto: &impl OpenMlsCrypto,
1695 ciphersuite: Ciphersuite,
1696 ) -> Result<PathGenerationSecret, VirtualClientsError> {
1697 let path_generation_secret =
1698 self.0
1699 .derive_secret(crypto, ciphersuite, PATH_GENERATION_LABEL)?;
1700 Ok(PathGenerationSecret(path_generation_secret))
1701 }
1702}
1703
1704#[derive(Debug)]
1713pub(crate) struct VcCommitMaterial {
1714 pub(crate) epoch_id: EpochId,
1716 pub(crate) operation_secret: OperationSecret,
1718 pub(crate) external_init_secret: Option<ExternalInitSecret>,
1720}
1721
1722#[derive(Debug, PartialEq, Eq)]
1746pub(crate) enum DerivationInfoTbe {
1747 LeafNode {
1752 leaf_index: LeafNodeIndex,
1753 generation: u32,
1754 external_init_secret: Option<ExternalInitSecret>,
1756 },
1757 KeyPackage {
1759 leaf_index: LeafNodeIndex,
1760 generation: u32,
1761 key_package_index: u32,
1762 },
1763}
1764
1765impl DerivationInfoTbe {
1766 pub(crate) fn leaf_index(&self) -> LeafNodeIndex {
1768 match self {
1769 Self::LeafNode { leaf_index, .. } | Self::KeyPackage { leaf_index, .. } => *leaf_index,
1770 }
1771 }
1772
1773 pub(crate) fn generation(&self) -> u32 {
1775 match self {
1776 Self::LeafNode { generation, .. } | Self::KeyPackage { generation, .. } => *generation,
1777 }
1778 }
1779
1780 pub(crate) fn external_init_secret(&self) -> Option<&ExternalInitSecret> {
1783 match self {
1784 Self::LeafNode {
1785 external_init_secret,
1786 ..
1787 } => external_init_secret.as_ref(),
1788 Self::KeyPackage { .. } => None,
1789 }
1790 }
1791
1792 fn tls_serialize_detached(&self) -> Result<Vec<u8>, tls_codec::Error> {
1796 match self {
1797 Self::LeafNode {
1798 leaf_index,
1799 generation,
1800 external_init_secret,
1801 } => {
1802 let mut out = Vec::with_capacity(
1803 leaf_index.tls_serialized_len()
1804 + generation.tls_serialized_len()
1805 + external_init_secret.tls_serialized_len(),
1806 );
1807 leaf_index.tls_serialize(&mut out)?;
1808 generation.tls_serialize(&mut out)?;
1809 external_init_secret.tls_serialize(&mut out)?;
1810 Ok(out)
1811 }
1812 Self::KeyPackage {
1813 leaf_index,
1814 generation,
1815 key_package_index,
1816 } => {
1817 let mut out = Vec::with_capacity(
1818 leaf_index.tls_serialized_len()
1819 + generation.tls_serialized_len()
1820 + key_package_index.tls_serialized_len(),
1821 );
1822 leaf_index.tls_serialize(&mut out)?;
1823 generation.tls_serialize(&mut out)?;
1824 key_package_index.tls_serialize(&mut out)?;
1825 Ok(out)
1826 }
1827 }
1828 }
1829
1830 fn deserialize_for_operation(
1836 bytes: &[u8],
1837 operation_type: VirtualClientOperationType,
1838 ) -> Result<Self, VirtualClientsError> {
1839 let (leaf_index, rest) = LeafNodeIndex::tls_deserialize_bytes(bytes)?;
1840 let (generation, rest) = u32::tls_deserialize_bytes(rest)?;
1841 let (tbe, rest) = match operation_type {
1842 VirtualClientOperationType::KeyPackage => {
1843 let (key_package_index, rest) = u32::tls_deserialize_bytes(rest)?;
1844 (
1845 Self::KeyPackage {
1846 leaf_index,
1847 generation,
1848 key_package_index,
1849 },
1850 rest,
1851 )
1852 }
1853 VirtualClientOperationType::LeafNode => {
1857 let (external_init_secret, rest) =
1858 Option::<ExternalInitSecret>::tls_deserialize_bytes(rest)?;
1859 (
1860 Self::LeafNode {
1861 leaf_index,
1862 generation,
1863 external_init_secret,
1864 },
1865 rest,
1866 )
1867 }
1868 VirtualClientOperationType::Application => {
1869 return Err(VirtualClientsError::DerivationInfoMalformed);
1870 }
1871 };
1872 if !rest.is_empty() {
1873 return Err(VirtualClientsError::DerivationInfoMalformed);
1874 }
1875 Ok(tbe)
1876 }
1877}
1878
1879pub(crate) fn load_vc_epoch_state_and_tree<Provider: OpenMlsProvider>(
1885 provider: &Provider,
1886 epoch_id: &EpochId,
1887) -> Result<
1888 (
1889 VcDerivationEpochState,
1890 crate::components::vc_operation_tree::OperationSecretTree,
1891 ),
1892 VirtualClientsError,
1893> {
1894 use openmls_traits::storage::StorageProvider as _;
1895
1896 let storage = provider.storage();
1897 let state = storage
1898 .vc_derivation_epoch_state(epoch_id)
1899 .map_err(|e| {
1900 log::error!("vc: load derivation epoch state failed: {e:?}");
1901 VirtualClientsError::StorageError
1902 })?
1903 .ok_or(VirtualClientsError::MissingDerivationEpochState)?;
1904 let operation_tree = storage
1905 .vc_operation_tree(epoch_id)
1906 .map_err(|e| {
1907 log::error!("vc: load operation tree failed: {e:?}");
1908 VirtualClientsError::StorageError
1909 })?
1910 .ok_or(VirtualClientsError::MissingOperationTree)?;
1911 Ok((state, operation_tree))
1912}
1913
1914pub(crate) fn resolve_vc_leaf_dictionary(
1926 caller_capabilities: Option<&crate::treesync::node::leaf_node::Capabilities>,
1927 caller_extensions: Option<
1928 &crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
1929 >,
1930 current_leaf: Option<&crate::treesync::node::leaf_node::LeafNode>,
1931) -> Result<crate::extensions::AppDataDictionary, VirtualClientsError> {
1932 use crate::{
1933 component::{ComponentId, ComponentType},
1934 extensions::ExtensionType,
1935 };
1936 use tls_codec::DeserializeBytes as _;
1937
1938 let supports_app_data_dictionary = match caller_capabilities {
1939 Some(c) => c.extensions().contains(&ExtensionType::AppDataDictionary),
1940 None => current_leaf
1941 .map(|leaf| {
1942 leaf.capabilities()
1943 .extensions()
1944 .contains(&ExtensionType::AppDataDictionary)
1945 })
1946 .unwrap_or(false),
1947 };
1948 if !supports_app_data_dictionary {
1949 return Err(VirtualClientsError::AppDataDictionaryNotSupported);
1950 }
1951
1952 let mut resolved_dictionary = current_leaf
1953 .and_then(|leaf| leaf.extensions().app_data_dictionary())
1954 .map(|ext| ext.dictionary().clone())
1955 .unwrap_or_default();
1956 if let Some(caller_dict) = caller_extensions.and_then(|exts| exts.app_data_dictionary()) {
1957 for entry in caller_dict.dictionary().entries() {
1958 resolved_dictionary.insert(entry.id(), entry.data().to_vec());
1959 }
1960 }
1961
1962 let app_components_bytes = resolved_dictionary
1963 .get(&ComponentId::from(ComponentType::AppComponents))
1964 .map(<[u8]>::to_vec);
1965 let Some(app_components_bytes) = app_components_bytes else {
1966 return Err(VirtualClientsError::VcComponentNotListed);
1967 };
1968
1969 let supported_components = Vec::<u16>::tls_deserialize_exact_bytes(&app_components_bytes)
1972 .map_err(|e| {
1973 log::error!("vc: AppComponents body failed to deserialize: {e:?}");
1974 VirtualClientsError::VcComponentNotListed
1975 })?;
1976 if !supported_components.contains(&VC_COMPONENT_ID) {
1977 return Err(VirtualClientsError::VcComponentNotListed);
1978 }
1979
1980 Ok(resolved_dictionary)
1981}
1982
1983pub(crate) fn merge_vc_derivation_info(
1991 caller_extensions: Option<
1992 &crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
1993 >,
1994 mut resolved_dictionary: crate::extensions::AppDataDictionary,
1995 derivation_info_bytes: Vec<u8>,
1996) -> Result<
1997 crate::extensions::Extensions<crate::treesync::node::leaf_node::LeafNode>,
1998 crate::error::LibraryError,
1999> {
2000 use crate::extensions::{AppDataDictionaryExtension, Extension, Extensions};
2001
2002 resolved_dictionary.insert(VC_COMPONENT_ID, derivation_info_bytes);
2003 let vc_extension =
2004 Extension::AppDataDictionary(AppDataDictionaryExtension::new(resolved_dictionary));
2005
2006 let other_extensions = caller_extensions
2007 .map(|exts| {
2008 exts.iter()
2009 .filter(|ext| !matches!(ext, Extension::AppDataDictionary(_)))
2010 .cloned()
2011 .collect::<Vec<_>>()
2012 })
2013 .unwrap_or_default();
2014 let new_extensions: Vec<Extension> = other_extensions
2015 .into_iter()
2016 .chain(std::iter::once(vc_extension))
2017 .collect();
2018 Extensions::from_vec(new_extensions)
2019 .map_err(|_| crate::error::LibraryError::custom("Failed to build VC leaf-node extensions"))
2020}
2021
2022#[cfg(test)]
2023mod tests {
2024 use super::*;
2025 use openmls_rust_crypto::{MemoryStorage, OpenMlsRustCrypto};
2026 use openmls_traits::{
2027 random::OpenMlsRand,
2028 storage::{StorageProvider, CURRENT_VERSION},
2029 OpenMlsProvider,
2030 };
2031
2032 const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
2033
2034 fn register_epoch_state(provider: &OpenMlsRustCrypto, leaf_index: LeafNodeIndex) -> EpochId {
2038 use crate::components::vc_operation_tree::OperationSecretTree;
2039
2040 let emulator = EmulatorEpochSecret::new(
2041 &provider
2042 .rand()
2043 .random_vec(CIPHERSUITE.hash_length())
2044 .expect("randomness"),
2045 );
2046 let epoch_id = emulator
2047 .derive_epoch_id(provider.crypto(), CIPHERSUITE)
2048 .expect("derive epoch id");
2049 let epoch_encryption_key = emulator
2050 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
2051 .expect("derive epoch encryption key");
2052 let reuse_guard_secret = emulator
2053 .derive_reuse_guard_secret(provider.crypto(), CIPHERSUITE)
2054 .expect("derive reuse guard secret");
2055 let generation_id_secret = emulator
2056 .derive_generation_id_secret(provider.crypto(), CIPHERSUITE)
2057 .expect("derive generation id secret");
2058 let epoch_base_secret = emulator
2059 .derive_epoch_base_secret(provider.crypto(), CIPHERSUITE)
2060 .expect("derive epoch base secret");
2061 let emulation_group_size = TreeSize::new(2);
2062 let state = VcDerivationEpochState::new(
2063 leaf_index,
2064 epoch_encryption_key,
2065 reuse_guard_secret,
2066 generation_id_secret,
2067 emulation_group_size,
2068 CIPHERSUITE,
2069 );
2070 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_derivation_epoch_state(
2071 provider.storage(),
2072 &epoch_id,
2073 &state,
2074 )
2075 .expect("write derivation epoch state");
2076 let operation_tree = OperationSecretTree::new(epoch_base_secret, emulation_group_size);
2077 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_operation_tree(
2078 provider.storage(),
2079 &epoch_id,
2080 &operation_tree,
2081 )
2082 .expect("write operation tree");
2083 epoch_id
2084 }
2085
2086 #[test]
2089 fn assemble_upload_reads_leaf_index_from_state() {
2090 let provider = OpenMlsRustCrypto::default();
2091 let leaf_index = LeafNodeIndex::new(5);
2092 let epoch_id = register_epoch_state(&provider, leaf_index);
2093 let infos = vec![
2094 KeyPackageInfo {
2095 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2096 cipher_suite: CIPHERSUITE,
2097 key_package_index: 0,
2098 },
2099 KeyPackageInfo {
2100 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2101 cipher_suite: CIPHERSUITE,
2102 key_package_index: 1,
2103 },
2104 ];
2105
2106 let upload = assemble_vc_key_package_upload(provider.storage(), epoch_id.clone(), 4, infos)
2107 .expect("assemble upload");
2108
2109 assert_eq!(upload.epoch_id, epoch_id);
2110 assert_eq!(upload.leaf_index, leaf_index);
2111 assert_eq!(upload.generation, 4);
2112 assert_eq!(upload.key_package_info.len(), 2);
2113 }
2114
2115 #[test]
2118 fn assemble_upload_without_state_fails() {
2119 let provider = OpenMlsRustCrypto::default();
2120 let epoch_id = EpochId(b"unregistered-epoch".to_vec().into());
2121 let err = assemble_vc_key_package_upload(provider.storage(), epoch_id, 0, Vec::new())
2122 .expect_err("assemble must fail without registered state");
2123 assert_eq!(err, VirtualClientsError::MissingDerivationEpochState);
2124 }
2125
2126 #[test]
2130 fn process_upload_stores_records() {
2131 let provider = OpenMlsRustCrypto::default();
2132 let leaf_index = LeafNodeIndex::new(0);
2133 let epoch_id = register_epoch_state(&provider, leaf_index);
2134 let ref_a = KeyPackageRef::from_slice(b"kp-ref-a");
2135 let ref_b = KeyPackageRef::from_slice(b"kp-ref-b");
2136 let upload = KeyPackageUpload {
2137 epoch_id: epoch_id.clone(),
2138 leaf_index,
2139 generation: 0,
2140 key_package_info: vec![
2141 KeyPackageInfo {
2142 key_package_ref: ref_a.clone(),
2143 cipher_suite: CIPHERSUITE,
2144 key_package_index: 0,
2145 },
2146 KeyPackageInfo {
2147 key_package_ref: ref_b.clone(),
2148 cipher_suite: CIPHERSUITE,
2149 key_package_index: 1,
2150 },
2151 ],
2152 };
2153
2154 process_vc_key_package_upload(&provider, &upload).expect("process upload");
2155
2156 let material_a: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2157 CURRENT_VERSION,
2158 >>::retained_key_package_material(
2159 provider.storage(), &ref_a
2160 )
2161 .expect("read material a")
2162 .expect("material a present");
2163 assert_eq!(material_a.epoch_id, epoch_id);
2164 assert_eq!(material_a.leaf_index, leaf_index);
2165 assert_eq!(material_a.generation, 0);
2166 assert_eq!(material_a.key_package_index, 0);
2167 assert_eq!(material_a.key_package_ciphersuite, CIPHERSUITE);
2168
2169 let material_b: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2170 CURRENT_VERSION,
2171 >>::retained_key_package_material(
2172 provider.storage(), &ref_b
2173 )
2174 .expect("read material b")
2175 .expect("material b present");
2176 assert_eq!(material_b.epoch_id, epoch_id);
2177 assert_eq!(material_b.leaf_index, leaf_index);
2178 assert_eq!(material_b.generation, 0);
2179 assert_eq!(material_b.key_package_index, 1);
2180 assert_eq!(material_b.key_package_ciphersuite, CIPHERSUITE);
2181 }
2182
2183 #[test]
2185 fn delete_key_package_removes_vc_record() {
2186 let provider = OpenMlsRustCrypto::default();
2187 let leaf_index = LeafNodeIndex::new(0);
2188 let epoch_id = register_epoch_state(&provider, leaf_index);
2189 let kp_ref = KeyPackageRef::from_slice(b"kp-ref");
2190 let upload = KeyPackageUpload {
2191 epoch_id,
2192 leaf_index,
2193 generation: 0,
2194 key_package_info: vec![KeyPackageInfo {
2195 key_package_ref: kp_ref.clone(),
2196 cipher_suite: CIPHERSUITE,
2197 key_package_index: 0,
2198 }],
2199 };
2200 process_vc_key_package_upload(&provider, &upload).expect("process upload");
2201
2202 let present: Option<RetainedKeyPackageMaterial> = <MemoryStorage as StorageProvider<
2203 CURRENT_VERSION,
2204 >>::retained_key_package_material(
2205 provider.storage(), &kp_ref
2206 )
2207 .expect("read material");
2208 assert!(present.is_some());
2209
2210 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::delete_key_package(
2211 provider.storage(),
2212 &kp_ref,
2213 )
2214 .expect("delete key package");
2215
2216 let after: Option<RetainedKeyPackageMaterial> = <MemoryStorage as StorageProvider<
2217 CURRENT_VERSION,
2218 >>::retained_key_package_material(
2219 provider.storage(), &kp_ref
2220 )
2221 .expect("read material after delete");
2222 assert!(after.is_none());
2223 }
2224
2225 fn setup_key_and_epoch_id(provider: &OpenMlsRustCrypto) -> (EpochEncryptionKey, EpochId) {
2226 let emulator = EmulatorEpochSecret::new(
2227 &provider
2228 .rand()
2229 .random_vec(CIPHERSUITE.hash_length())
2230 .expect("randomness"),
2231 );
2232 let key = emulator
2233 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
2234 .expect("derive ek");
2235 let epoch_id = emulator
2236 .derive_epoch_id(provider.crypto(), CIPHERSUITE)
2237 .expect("derive epoch id");
2238 (key, epoch_id)
2239 }
2240
2241 #[test]
2247 fn derivation_info_tbe_roundtrip() {
2248 let provider = OpenMlsRustCrypto::default();
2249 let (key, epoch_id) = setup_key_and_epoch_id(&provider);
2250 let leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
2251
2252 let key_package_tbe = DerivationInfoTbe::KeyPackage {
2253 leaf_index: LeafNodeIndex::new(7),
2254 generation: 3,
2255 key_package_index: 5,
2256 };
2257 let leaf_node_tbe = DerivationInfoTbe::LeafNode {
2258 leaf_index: LeafNodeIndex::new(7),
2259 generation: 3,
2260 external_init_secret: None,
2261 };
2262 let external_commit_tbe = DerivationInfoTbe::LeafNode {
2263 leaf_index: LeafNodeIndex::new(7),
2264 generation: 3,
2265 external_init_secret: Some(ExternalInitSecret::from_slice(b"external init secret")),
2266 };
2267
2268 let key_package_bytes = key_package_tbe
2272 .tls_serialize_detached()
2273 .expect("serialize key package tbe");
2274 let leaf_node_bytes = leaf_node_tbe
2275 .tls_serialize_detached()
2276 .expect("serialize leaf node tbe");
2277 assert_eq!(key_package_bytes.len(), leaf_node_bytes.len() + 3);
2278
2279 for (original, operation_type) in [
2280 (key_package_tbe, VirtualClientOperationType::KeyPackage),
2281 (leaf_node_tbe, VirtualClientOperationType::LeafNode),
2282 (external_commit_tbe, VirtualClientOperationType::LeafNode),
2283 ] {
2284 let derivation_info = DerivationInfo::encrypt(
2285 provider.crypto(),
2286 CIPHERSUITE,
2287 &key,
2288 epoch_id.clone(),
2289 &leaf_encryption_key,
2290 &original,
2291 )
2292 .expect("encrypt");
2293 assert_eq!(derivation_info.epoch_id(), &epoch_id);
2294 let decrypted = derivation_info
2295 .decrypt(
2296 provider.crypto(),
2297 CIPHERSUITE,
2298 &key,
2299 &leaf_encryption_key,
2300 operation_type,
2301 )
2302 .expect("decrypt");
2303 assert_eq!(original, decrypted);
2304 }
2305 }
2306
2307 #[test]
2313 fn derivation_info_tbe_wire_format_matches_spec() {
2314 let absent = DerivationInfoTbe::LeafNode {
2315 leaf_index: LeafNodeIndex::new(7),
2316 generation: 3,
2317 external_init_secret: None,
2318 }
2319 .tls_serialize_detached()
2320 .expect("serialize");
2321 assert_eq!(
2322 absent,
2323 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00]
2324 );
2325
2326 let present = DerivationInfoTbe::LeafNode {
2327 leaf_index: LeafNodeIndex::new(7),
2328 generation: 3,
2329 external_init_secret: Some(ExternalInitSecret::from_slice(b"init")),
2330 }
2331 .tls_serialize_detached()
2332 .expect("serialize");
2333 assert_eq!(
2334 present,
2335 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x01, 0x04, b'i', b'n', b'i', b't']
2336 );
2337
2338 let key_package = DerivationInfoTbe::KeyPackage {
2339 leaf_index: LeafNodeIndex::new(7),
2340 generation: 3,
2341 key_package_index: 5,
2342 }
2343 .tls_serialize_detached()
2344 .expect("serialize");
2345 assert_eq!(
2346 key_package,
2347 [0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05]
2348 );
2349 }
2350
2351 #[test]
2356 fn derivation_info_tbe_rejects_trailing_data() {
2357 let variants = [
2358 (
2359 DerivationInfoTbe::LeafNode {
2360 leaf_index: LeafNodeIndex::new(7),
2361 generation: 3,
2362 external_init_secret: None,
2363 },
2364 VirtualClientOperationType::LeafNode,
2365 ),
2366 (
2367 DerivationInfoTbe::KeyPackage {
2368 leaf_index: LeafNodeIndex::new(7),
2369 generation: 3,
2370 key_package_index: 5,
2371 },
2372 VirtualClientOperationType::KeyPackage,
2373 ),
2374 ];
2375 for (tbe, operation_type) in variants {
2376 let mut bytes = tbe.tls_serialize_detached().expect("serialize");
2377 bytes.push(0x00);
2378 let result = DerivationInfoTbe::deserialize_for_operation(&bytes, operation_type);
2379 assert_eq!(result, Err(VirtualClientsError::DerivationInfoMalformed));
2380 }
2381 }
2382
2383 #[test]
2385 fn external_init_secret_debug_is_redacted() {
2386 let tbe = DerivationInfoTbe::LeafNode {
2387 leaf_index: LeafNodeIndex::new(7),
2388 generation: 3,
2389 external_init_secret: Some(ExternalInitSecret::from_slice(b"very secret bytes")),
2390 };
2391 let debug = format!("{tbe:?}");
2392 assert!(debug.contains("<redacted>"));
2393 assert!(!debug.contains("secret bytes"));
2394 assert!(!debug.to_lowercase().contains("76657279"));
2395 }
2396
2397 #[test]
2402 fn decryption_fails_with_wrong_leaf_encryption_key() {
2403 let provider = OpenMlsRustCrypto::default();
2404 let (key, epoch_id) = setup_key_and_epoch_id(&provider);
2405 let leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
2406 let tbe = DerivationInfoTbe::LeafNode {
2407 leaf_index: LeafNodeIndex::new(1),
2408 generation: 0,
2409 external_init_secret: None,
2410 };
2411 let derivation_info = DerivationInfo::encrypt(
2412 provider.crypto(),
2413 CIPHERSUITE,
2414 &key,
2415 epoch_id,
2416 &leaf_encryption_key,
2417 &tbe,
2418 )
2419 .expect("encrypt");
2420 let other_leaf_encryption_key = provider.rand().random_vec(32).expect("randomness");
2421 let err = derivation_info
2422 .decrypt(
2423 provider.crypto(),
2424 CIPHERSUITE,
2425 &key,
2426 &other_leaf_encryption_key,
2427 VirtualClientOperationType::LeafNode,
2428 )
2429 .expect_err("decryption with the wrong context must fail");
2430 assert_eq!(err, VirtualClientsError::DerivationInfoDecryptionFailed);
2431 }
2432
2433 #[test]
2437 fn key_package_seed_derivation_is_indexed_and_label_separated() {
2438 let provider = OpenMlsRustCrypto::default();
2439 let operation_secret = OperationSecret::from(Secret::from_slice(
2440 &provider
2441 .rand()
2442 .random_vec(CIPHERSUITE.hash_length())
2443 .expect("randomness"),
2444 ));
2445
2446 let seed_zero = operation_secret
2447 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2448 .expect("derive seed 0");
2449 let seed_zero_again = operation_secret
2450 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2451 .expect("derive seed 0 again");
2452 let seed_one = operation_secret
2453 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 1)
2454 .expect("derive seed 1");
2455
2456 let init_zero = seed_zero
2457 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2458 .expect("derive init key 0")
2459 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2460 .expect("generate init pair 0");
2461 let init_zero_again = seed_zero_again
2462 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2463 .expect("derive init key 0 again")
2464 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2465 .expect("generate init pair 0 again");
2466 let init_one = seed_one
2467 .derive_init_key_secret(provider.crypto(), CIPHERSUITE)
2468 .expect("derive init key 1")
2469 .generate_init_key_pair(provider.crypto(), CIPHERSUITE)
2470 .expect("generate init pair 1");
2471
2472 assert_eq!(init_zero.public, init_zero_again.public);
2474 assert_ne!(init_zero.public, init_one.public);
2476
2477 let encryption_zero = seed_zero
2479 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2480 .expect("derive encryption key 0")
2481 .generate_encryption_key_pair(provider.crypto(), CIPHERSUITE)
2482 .expect("generate encryption pair 0");
2483 assert_ne!(
2484 init_zero.public.as_slice(),
2485 encryption_zero.public_key().as_slice()
2486 );
2487 }
2488
2489 #[test]
2494 fn key_package_seed_binds_target_ciphersuite() {
2495 let provider = OpenMlsRustCrypto::default();
2496 let operation_secret = OperationSecret::from(Secret::from_slice(
2497 &provider
2498 .rand()
2499 .random_vec(CIPHERSUITE.hash_length())
2500 .expect("randomness"),
2501 ));
2502 let other_ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519;
2505
2506 let seed = operation_secret
2507 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2508 .expect("derive seed");
2509 let seed_other_suite = operation_secret
2510 .derive_key_package_seed_secret(provider.crypto(), other_ciphersuite, 0)
2511 .expect("derive seed under other target ciphersuite");
2512
2513 assert_ne!(seed.0.as_slice(), seed_other_suite.0.as_slice());
2514 }
2515
2516 #[test]
2521 fn target_operation_secret_binds_ciphersuite_and_group_id() {
2522 let provider = OpenMlsRustCrypto::default();
2523 let operation_secret = OperationSecret::from(Secret::from_slice(
2524 &provider
2525 .rand()
2526 .random_vec(CIPHERSUITE.hash_length())
2527 .expect("randomness"),
2528 ));
2529 let group_id = GroupId::from_slice(b"group-a");
2530 let other_ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519;
2531
2532 let target = operation_secret
2533 .derive_target_operation_secret(provider.crypto(), CIPHERSUITE, &group_id)
2534 .expect("derive target operation secret");
2535 let target_again = operation_secret
2536 .derive_target_operation_secret(provider.crypto(), CIPHERSUITE, &group_id)
2537 .expect("derive target operation secret again");
2538 let target_other_group = operation_secret
2539 .derive_target_operation_secret(
2540 provider.crypto(),
2541 CIPHERSUITE,
2542 &GroupId::from_slice(b"group-b"),
2543 )
2544 .expect("derive target operation secret for other group");
2545 let target_other_suite = operation_secret
2546 .derive_target_operation_secret(provider.crypto(), other_ciphersuite, &group_id)
2547 .expect("derive target operation secret under other target ciphersuite");
2548
2549 assert_eq!(target.0.as_slice(), target_again.0.as_slice());
2551 assert_ne!(target.0.as_slice(), target_other_group.0.as_slice());
2554 assert_ne!(target.0.as_slice(), target_other_suite.0.as_slice());
2555
2556 let encryption_key_secret = target
2559 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2560 .expect("derive encryption key secret");
2561 let path_generation_secret = target
2562 .derive_path_generation_secret(provider.crypto(), CIPHERSUITE)
2563 .expect("derive path generation secret");
2564 assert_ne!(
2565 encryption_key_secret.0.as_slice(),
2566 path_generation_secret.0.as_slice()
2567 );
2568 }
2569
2570 #[test]
2574 fn group_creation_secret_derivation_is_deterministic_and_label_separated() {
2575 let provider = OpenMlsRustCrypto::default();
2576 let operation_secret = OperationSecret::from(Secret::from_slice(
2577 &provider
2578 .rand()
2579 .random_vec(CIPHERSUITE.hash_length())
2580 .expect("randomness"),
2581 ));
2582
2583 let seed_zero = operation_secret
2584 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 0)
2585 .expect("derive seed 0");
2586 let seed_one = operation_secret
2587 .derive_key_package_seed_secret(provider.crypto(), CIPHERSUITE, 1)
2588 .expect("derive seed 1");
2589
2590 let epoch_secret_zero = seed_zero
2591 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2592 .expect("derive group creation secret 0");
2593 let epoch_secret_zero_again = seed_zero
2594 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2595 .expect("derive group creation secret 0 again");
2596 let epoch_secret_one = seed_one
2597 .derive_group_creation_secret(provider.crypto(), CIPHERSUITE)
2598 .expect("derive group creation secret 1");
2599
2600 assert_eq!(
2602 epoch_secret_zero.as_slice(),
2603 epoch_secret_zero_again.as_slice()
2604 );
2605 assert_ne!(epoch_secret_zero.as_slice(), epoch_secret_one.as_slice());
2607
2608 let encryption_key_secret = seed_zero
2611 .derive_encryption_key_secret(provider.crypto(), CIPHERSUITE)
2612 .expect("derive encryption key 0");
2613 assert_ne!(
2614 epoch_secret_zero.as_slice(),
2615 encryption_key_secret.0.as_slice()
2616 );
2617 }
2618
2619 #[test]
2622 fn validate_rejects_duplicate_index() {
2623 let infos = vec![
2624 KeyPackageInfo {
2625 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2626 cipher_suite: CIPHERSUITE,
2627 key_package_index: 2,
2628 },
2629 KeyPackageInfo {
2630 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2631 cipher_suite: CIPHERSUITE,
2632 key_package_index: 2,
2633 },
2634 ];
2635 let err = validate_key_package_infos(&infos).expect_err("duplicate index must be rejected");
2636 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageIndex(2));
2637 }
2638
2639 #[test]
2641 fn validate_rejects_duplicate_ref() {
2642 let infos = vec![
2643 KeyPackageInfo {
2644 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2645 cipher_suite: CIPHERSUITE,
2646 key_package_index: 0,
2647 },
2648 KeyPackageInfo {
2649 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2650 cipher_suite: CIPHERSUITE,
2651 key_package_index: 1,
2652 },
2653 ];
2654 let err = validate_key_package_infos(&infos).expect_err("duplicate ref must be rejected");
2655 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageRef);
2656 }
2657
2658 #[test]
2660 fn validate_accepts_distinct_infos() {
2661 let infos = vec![
2662 KeyPackageInfo {
2663 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-a"),
2664 cipher_suite: CIPHERSUITE,
2665 key_package_index: 0,
2666 },
2667 KeyPackageInfo {
2668 key_package_ref: KeyPackageRef::from_slice(b"kp-ref-b"),
2669 cipher_suite: CIPHERSUITE,
2670 key_package_index: 1,
2671 },
2672 ];
2673 validate_key_package_infos(&infos).expect("distinct infos must pass");
2674 }
2675
2676 #[test]
2680 fn process_upload_rejects_malformed_without_consuming_generation() {
2681 let provider = OpenMlsRustCrypto::default();
2682 let leaf_index = LeafNodeIndex::new(0);
2683 let epoch_id = register_epoch_state(&provider, leaf_index);
2684 let ref_a = KeyPackageRef::from_slice(b"kp-ref-a");
2685 let ref_b = KeyPackageRef::from_slice(b"kp-ref-b");
2686
2687 let malformed = KeyPackageUpload {
2688 epoch_id: epoch_id.clone(),
2689 leaf_index,
2690 generation: 0,
2691 key_package_info: vec![
2692 KeyPackageInfo {
2693 key_package_ref: ref_a.clone(),
2694 cipher_suite: CIPHERSUITE,
2695 key_package_index: 0,
2696 },
2697 KeyPackageInfo {
2698 key_package_ref: ref_b.clone(),
2699 cipher_suite: CIPHERSUITE,
2700 key_package_index: 0,
2701 },
2702 ],
2703 };
2704 let err = process_vc_key_package_upload(&provider, &malformed)
2705 .expect_err("malformed upload must be rejected");
2706 assert_eq!(err, VirtualClientsError::DuplicateKeyPackageIndex(0));
2707
2708 let valid = KeyPackageUpload {
2709 epoch_id: epoch_id.clone(),
2710 leaf_index,
2711 generation: 0,
2712 key_package_info: vec![
2713 KeyPackageInfo {
2714 key_package_ref: ref_a.clone(),
2715 cipher_suite: CIPHERSUITE,
2716 key_package_index: 0,
2717 },
2718 KeyPackageInfo {
2719 key_package_ref: ref_b.clone(),
2720 cipher_suite: CIPHERSUITE,
2721 key_package_index: 1,
2722 },
2723 ],
2724 };
2725 process_vc_key_package_upload(&provider, &valid)
2726 .expect("valid upload reusing the same generation must succeed");
2727
2728 let material_a: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2729 CURRENT_VERSION,
2730 >>::retained_key_package_material(
2731 provider.storage(), &ref_a
2732 )
2733 .expect("read material a")
2734 .expect("material a present");
2735 assert_eq!(material_a.epoch_id, epoch_id);
2736 assert_eq!(material_a.generation, 0);
2737 assert_eq!(material_a.key_package_index, 0);
2738
2739 let material_b: RetainedKeyPackageMaterial = <MemoryStorage as StorageProvider<
2740 CURRENT_VERSION,
2741 >>::retained_key_package_material(
2742 provider.storage(), &ref_b
2743 )
2744 .expect("read material b")
2745 .expect("material b present");
2746 assert_eq!(material_b.key_package_index, 1);
2747 }
2748
2749 fn state_from_secret_bytes(
2752 provider: &OpenMlsRustCrypto,
2753 secret_bytes: &[u8],
2754 leaf_index: LeafNodeIndex,
2755 ) -> VcDerivationEpochState {
2756 let emulator = EmulatorEpochSecret::new(secret_bytes);
2757 let epoch_encryption_key = emulator
2758 .derive_epoch_encryption_key(provider.crypto(), CIPHERSUITE)
2759 .expect("derive epoch encryption key");
2760 let reuse_guard_secret = emulator
2761 .derive_reuse_guard_secret(provider.crypto(), CIPHERSUITE)
2762 .expect("derive reuse guard secret");
2763 let generation_id_secret = emulator
2764 .derive_generation_id_secret(provider.crypto(), CIPHERSUITE)
2765 .expect("derive generation id secret");
2766 VcDerivationEpochState::new(
2767 leaf_index,
2768 epoch_encryption_key,
2769 reuse_guard_secret,
2770 generation_id_secret,
2771 TreeSize::new(2),
2772 CIPHERSUITE,
2773 )
2774 }
2775
2776 #[test]
2781 fn generation_id_is_deterministic_and_context_sensitive() {
2782 let provider = OpenMlsRustCrypto::default();
2783 let secret_bytes = provider
2784 .rand()
2785 .random_vec(CIPHERSUITE.hash_length())
2786 .expect("randomness");
2787 let state = state_from_secret_bytes(&provider, &secret_bytes, LeafNodeIndex::new(0));
2788
2789 let group_id = GroupId::from_slice(b"higher-level-group");
2790 let epoch = GroupEpoch::from(7);
2791 let derive = |group_id: &GroupId, epoch, generation, ratchet_type| {
2792 state
2793 .derive_generation_id(provider.crypto(), group_id, epoch, generation, ratchet_type)
2794 .expect("derive generation id")
2795 };
2796
2797 let base = derive(&group_id, epoch, 3, RatchetType::Application);
2798 assert_eq!(base.as_slice().len(), CIPHERSUITE.hash_length());
2800 assert_eq!(base, derive(&group_id, epoch, 3, RatchetType::Application));
2802 assert_ne!(base, derive(&group_id, epoch, 4, RatchetType::Application));
2805 assert_ne!(
2806 base,
2807 derive(&group_id, GroupEpoch::from(8), 3, RatchetType::Application)
2808 );
2809 assert_ne!(
2810 base,
2811 derive(
2812 &GroupId::from_slice(b"other-group"),
2813 epoch,
2814 3,
2815 RatchetType::Application
2816 )
2817 );
2818 assert_ne!(base, derive(&group_id, epoch, 3, RatchetType::Handshake));
2819
2820 let sibling = state_from_secret_bytes(&provider, &secret_bytes, LeafNodeIndex::new(5));
2824 let sibling_id = sibling
2825 .derive_generation_id(
2826 provider.crypto(),
2827 &group_id,
2828 epoch,
2829 3,
2830 RatchetType::Application,
2831 )
2832 .expect("sibling derive generation id");
2833 assert_eq!(base, sibling_id);
2834 }
2835
2836 #[test]
2837 #[allow(deprecated)]
2838 fn legacy_registration_record_layout_is_frozen() {
2839 let record = RegisteredVcDerivationEpoch {
2840 group_epoch: GroupEpoch::from(7),
2841 epoch_id: EpochId::new(vec![1, 2, 3]),
2842 };
2843 let json = serde_json::to_string(&record).expect("serialize legacy record");
2844 assert_eq!(json, r#"{"group_epoch":7,"epoch_id":[1,2,3]}"#);
2845 let decoded: RegisteredVcDerivationEpoch =
2846 serde_json::from_str(&json).expect("deserialize legacy record");
2847 assert_eq!(decoded, record);
2848 }
2849
2850 #[test]
2851 #[allow(deprecated)]
2852 fn legacy_bindings_record_layout_is_frozen() {
2853 let record = VcEmulationBindings {
2854 bindings: VecDeque::from([
2855 (GroupEpoch::from(7), EpochId::new(vec![1, 2, 3])),
2856 (GroupEpoch::from(8), EpochId::new(vec![4, 5, 6])),
2857 ]),
2858 };
2859 let json = serde_json::to_string(&record).expect("serialize legacy record");
2860 assert_eq!(json, r#"{"bindings":[[7,[1,2,3]],[8,[4,5,6]]]}"#);
2861 let decoded: VcEmulationBindings =
2862 serde_json::from_str(&json).expect("deserialize legacy record");
2863 assert_eq!(decoded, record);
2864
2865 let entries = decoded.into_entries();
2866 assert_eq!(
2867 entries,
2868 vec![
2869 (GroupEpoch::from(7), EpochId::new(vec![1, 2, 3])),
2870 (GroupEpoch::from(8), EpochId::new(vec![4, 5, 6])),
2871 ]
2872 );
2873 }
2874
2875 #[test]
2876 fn log_entry_layout_is_frozen() {
2877 let entry = VcDerivationEpochLogEntry {
2878 sequence: 2,
2879 group_epoch: GroupEpoch::from(7),
2880 epoch_id: EpochId::new(vec![1, 2, 3]),
2881 registered_at: SystemTime::UNIX_EPOCH + std::time::Duration::new(1_700_000_000, 42),
2882 };
2883 let json = serde_json::to_string(&entry).expect("serialize log entry");
2884 assert_eq!(
2885 json,
2886 r#"{"sequence":2,"group_epoch":7,"epoch_id":[1,2,3],"registered_at":{"secs_since_epoch":1700000000,"nanos_since_epoch":42}}"#
2887 );
2888 let decoded: VcDerivationEpochLogEntry =
2889 serde_json::from_str(&json).expect("deserialize log entry");
2890 assert_eq!(decoded, entry);
2891 }
2892
2893 #[test]
2894 fn binding_layout_is_frozen() {
2895 let binding = VcEmulationBinding {
2896 group_epoch: GroupEpoch::from(7),
2897 epoch_id: EpochId::new(vec![1, 2, 3]),
2898 };
2899 let json = serde_json::to_string(&binding).expect("serialize binding");
2900 assert_eq!(json, r#"{"group_epoch":7,"epoch_id":[1,2,3]}"#);
2901 let decoded: VcEmulationBinding = serde_json::from_str(&json).expect("deserialize binding");
2902 assert_eq!(decoded, binding);
2903 }
2904
2905 #[test]
2906 fn binding_from_legacy_record() {
2907 let epoch_id = EpochId::new(vec![4, 5, 6]);
2908 let binding = VcEmulationBinding::from_legacy_record(GroupEpoch::from(3), epoch_id.clone());
2909 assert_eq!(binding.group_epoch, GroupEpoch::from(3));
2910 assert_eq!(binding.epoch_id(), &epoch_id);
2911 }
2912
2913 #[test]
2914 fn log_entry_from_legacy_record() {
2915 let epoch_id = EpochId::new(vec![4, 5, 6]);
2916 let registered_at = SystemTime::UNIX_EPOCH;
2917 let entry = VcDerivationEpochLogEntry::from_legacy_record(
2918 GroupEpoch::from(3),
2919 epoch_id.clone(),
2920 registered_at,
2921 );
2922
2923 assert_eq!(entry.sequence, 0);
2924 assert_eq!(entry.group_epoch, GroupEpoch::from(3));
2925 assert_eq!(entry.epoch_id(), &epoch_id);
2926 assert_eq!(entry.registered_at, registered_at);
2927
2928 let mut log = VcDerivationEpochLog {
2931 entries: VecDeque::from([entry]),
2932 };
2933 assert!(log.shrink_to(1).is_empty());
2934 assert!(log.drop_superseded_before(SystemTime::now()).is_empty());
2935 assert_eq!(
2936 log.newest().map(|entry| entry.epoch_id.clone()),
2937 Some(epoch_id)
2938 );
2939 }
2940
2941 fn log_entry(
2942 group_epoch: u64,
2943 epoch_id: &EpochId,
2944 registered_at: SystemTime,
2945 ) -> VcDerivationEpochLogEntry {
2946 VcDerivationEpochLogEntry {
2947 sequence: group_epoch,
2948 group_epoch: GroupEpoch::from(group_epoch),
2949 epoch_id: epoch_id.clone(),
2950 registered_at,
2951 }
2952 }
2953
2954 #[test]
2955 fn log_reconstruction_orders_by_sequence() {
2956 let provider = OpenMlsRustCrypto::default();
2957 let group_id = GroupId::from_slice(b"emulation-group");
2958 let first = log_entry(0, &EpochId::new(vec![1]), SystemTime::UNIX_EPOCH);
2959 let second = log_entry(1, &EpochId::new(vec![2]), SystemTime::UNIX_EPOCH);
2960 let third = log_entry(2, &EpochId::new(vec![3]), SystemTime::UNIX_EPOCH);
2961 for entry in [&second, &third, &first] {
2964 <MemoryStorage as StorageProvider<CURRENT_VERSION>>::write_vc_derivation_epoch_log_entry(
2965 provider.storage(),
2966 &group_id,
2967 &entry.epoch_id,
2968 entry,
2969 )
2970 .expect("write log entry");
2971 }
2972
2973 let log = VcDerivationEpochLog::load(provider.storage(), &group_id).expect("load the log");
2974 assert_eq!(
2975 log.newest().map(|entry| entry.epoch_id.clone()),
2976 Some(third.epoch_id.clone())
2977 );
2978 assert_eq!(
2979 newest_vc_derivation_epoch(provider.storage(), &group_id).expect("newest epoch"),
2980 Some(third.epoch_id.clone())
2981 );
2982
2983 let mut log = log;
2985 assert_eq!(log.shrink_to(2), vec![first.epoch_id.clone()]);
2986 assert_eq!(
2987 log.newest().map(|entry| entry.epoch_id.clone()),
2988 Some(third.epoch_id)
2989 );
2990 }
2991
2992 #[test]
2993 fn wall_clock_sweep_measures_from_supersession() {
2994 let old = EpochId::new(vec![1]);
2995 let mid = EpochId::new(vec![2]);
2996 let new = EpochId::new(vec![3]);
2997 let start = SystemTime::UNIX_EPOCH;
2998 let minutes = |m: u64| std::time::Duration::from_secs(m * 60);
2999 let mut log = VcDerivationEpochLog {
3002 entries: VecDeque::from([
3003 log_entry(0, &old, start),
3004 log_entry(1, &mid, start + minutes(23 * 60 + 50)),
3005 log_entry(2, &new, start + minutes(24 * 60)),
3006 ]),
3007 };
3008
3009 assert!(log.drop_superseded_before(start + minutes(5)).is_empty());
3012 assert_eq!(
3015 log.drop_superseded_before(start + minutes(23 * 60 + 55)),
3016 vec![old]
3017 );
3018 assert_eq!(
3020 log.drop_superseded_before(start + minutes(48 * 60)),
3021 vec![mid]
3022 );
3023 assert_eq!(log.newest().map(|entry| entry.epoch_id.clone()), Some(new));
3024 }
3025}