1use openmls_traits::{
2 crypto::OpenMlsCrypto,
3 random::OpenMlsRand,
4 types::{Ciphersuite, CryptoError},
5};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use tls_codec::{DeserializeBytes, Serialize as _, TlsDeserializeBytes, TlsSerialize, TlsSize};
9
10use crate::{
11 binary_tree::{array_representation::TreeSize, LeafNodeIndex},
12 ciphersuite::Secret,
13 messages::PathSecret,
14 schedule::pprf::{Pprf, PprfError, Prefix256},
15 treesync::node::encryption_keys::EncryptionKeyPair,
16};
17
18pub const VC_COMPONENT_ID: u16 = 0x000D;
23
24const ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
30const PATH_GENERATION_LABEL: &str = "Path Generation";
31
32const EPOCH_ID_LABEL: &str = "Epoch ID";
33const EPOCH_ENCRYPTION_KEY_LABEL: &str = "Encryption Key";
34const EPOCH_SECRET_LABEL: &str = "Base Secret";
35const REUSE_GUARD_LABEL: &str = "Reuse Guard";
37const REUSE_GUARD_PRP_KEY_LABEL: &str = "reuse guard";
40const PRP_KEY_LEN: usize = 16;
42
43pub(crate) type VcPprf = Pprf<Prefix256>;
47
48#[derive(Debug)]
73pub struct VcEmulation {
74 pub epoch_id: EpochId,
77}
78
79#[derive(Error, Debug, PartialEq, Clone)]
81pub enum VirtualClientsError {
82 #[error("Failed to deserialize derivation info.")]
84 DerivationInfoMalformed,
85 #[error("Failed to decrypt epoch info.")]
88 EpochInfoDecryptionFailed,
89 #[error("No virtual-clients epoch encryption key for this epoch.")]
91 MissingEpochEncryptionKey,
92 #[error("No virtual-clients PPRF for this epoch.")]
94 MissingPprf,
95 #[error("No virtual-clients emulation-epoch state for this epoch.")]
98 MissingEmulationEpochState,
99 #[error("Virtual-clients storage error")]
102 StorageError,
103 #[error("Leaf encryption key from path does not match the derived key.")]
106 EncryptionKeyMismatch,
107 #[error("PPRF evaluation failed: {0}")]
110 PprfError(#[from] PprfError),
111 #[error("Cryptographic operation failed.")]
113 CryptoError(#[from] CryptoError),
114 #[error(
116 "Hash function produced output of length {actual_length}, expected {expected_length}."
117 )]
118 HashOutputLengthMismatch {
119 actual_length: usize,
121 expected_length: usize,
123 },
124 #[error("Random byte generation failed.")]
126 RandError,
127 #[error("TLS codec error: {0}")]
131 Tls(#[from] tls_codec::Error),
132 #[error("Leaf does not declare AppDataDictionary support in its capabilities.")]
135 AppDataDictionaryNotSupported,
136 #[error("Leaf's AppComponents entry does not list the virtual-clients component id.")]
140 VcComponentNotListed,
141}
142
143#[derive(Serialize, Deserialize)]
149pub(crate) struct EmulatorEpochSecret(Secret);
150
151impl std::fmt::Debug for EmulatorEpochSecret {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 f.debug_struct("EmulatorEpochSecret")
154 .field("secret", &"<redacted>")
155 .finish()
156 }
157}
158
159impl EmulatorEpochSecret {
160 pub(crate) fn new(bytes: &[u8]) -> Self {
164 Self(Secret::from_slice(bytes))
165 }
166
167 pub(crate) fn derive_epoch_id(
168 &self,
169 crypto: &impl OpenMlsCrypto,
170 ciphersuite: Ciphersuite,
171 ) -> Result<EpochId, VirtualClientsError> {
172 let secret = self.0.derive_secret(crypto, ciphersuite, EPOCH_ID_LABEL)?;
173 Ok(EpochId(secret.as_slice().to_vec()))
174 }
175
176 pub(crate) fn derive_epoch_encryption_key(
177 &self,
178 crypto: &impl OpenMlsCrypto,
179 ciphersuite: Ciphersuite,
180 ) -> Result<EpochEncryptionKey, VirtualClientsError> {
181 let secret = self.0.kdf_expand_label(
182 crypto,
183 ciphersuite,
184 EPOCH_ENCRYPTION_KEY_LABEL,
185 &[],
186 ciphersuite.aead_key_length(),
187 )?;
188 Ok(EpochEncryptionKey(secret))
189 }
190
191 pub(crate) fn derive_epoch_secret(
192 &self,
193 crypto: &impl OpenMlsCrypto,
194 ciphersuite: Ciphersuite,
195 ) -> Result<Secret, VirtualClientsError> {
196 Ok(self
197 .0
198 .derive_secret(crypto, ciphersuite, EPOCH_SECRET_LABEL)?)
199 }
200
201 pub(crate) fn derive_reuse_guard_secret(
203 &self,
204 crypto: &impl OpenMlsCrypto,
205 ciphersuite: Ciphersuite,
206 ) -> Result<ReuseGuardSecret, VirtualClientsError> {
207 let secret = self
208 .0
209 .derive_secret(crypto, ciphersuite, REUSE_GUARD_LABEL)?;
210 Ok(ReuseGuardSecret(secret))
211 }
212}
213
214#[derive(Serialize, Deserialize)]
218pub(crate) struct ReuseGuardSecret(Secret);
219
220impl std::fmt::Debug for ReuseGuardSecret {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 f.debug_struct("ReuseGuardSecret")
223 .field("secret", &"<redacted>")
224 .finish()
225 }
226}
227
228impl ReuseGuardSecret {
229 #[cfg(test)]
231 pub(crate) fn from_secret_for_tests(secret: Secret) -> Self {
232 Self(secret)
233 }
234
235 pub(crate) fn derive_prp_key(
245 &self,
246 crypto: &impl OpenMlsCrypto,
247 ciphersuite: Ciphersuite,
248 key_schedule_nonce: &[u8],
249 ) -> Result<[u8; PRP_KEY_LEN], VirtualClientsError> {
250 let key = self.0.kdf_expand_label(
251 crypto,
252 ciphersuite,
253 REUSE_GUARD_PRP_KEY_LABEL,
254 key_schedule_nonce,
255 PRP_KEY_LEN,
256 )?;
257 key.as_slice()
258 .try_into()
259 .map_err(|_| VirtualClientsError::HashOutputLengthMismatch {
260 actual_length: key.as_slice().len(),
261 expected_length: PRP_KEY_LEN,
262 })
263 }
264}
265
266pub(crate) fn build_vc_pprf(epoch_secret: Secret) -> VcPprf {
278 VcPprf::new_with_size(epoch_secret, TreeSize::from_leaf_count(u16::MAX as u32))
279}
280
281#[derive(Debug, TlsSize, TlsSerialize, TlsDeserializeBytes)]
282pub(crate) struct DerivationInfo {
283 epoch_id: EpochId,
284 ciphertext: EncryptedEpochInfo,
285}
286
287impl DerivationInfo {
288 pub(crate) fn new(epoch_id: EpochId, ciphertext: EncryptedEpochInfo) -> Self {
289 Self {
290 epoch_id,
291 ciphertext,
292 }
293 }
294
295 pub(crate) fn epoch_id(&self) -> &EpochId {
296 &self.epoch_id
297 }
298
299 pub(crate) fn decrypt(
300 &self,
301 crypto: &impl OpenMlsCrypto,
302 ciphersuite: Ciphersuite,
303 key: &EpochEncryptionKey,
304 ) -> Result<EpochInfoTbe, VirtualClientsError> {
305 self.ciphertext
306 .decrypt(crypto, ciphersuite, key, &self.epoch_id)
307 }
308}
309
310#[derive(
317 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TlsSize, TlsSerialize, TlsDeserializeBytes,
318)]
319pub struct EpochId(Vec<u8>);
320
321#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
333pub struct VcEmulationBindings {
334 bindings: std::collections::VecDeque<(crate::group::GroupEpoch, EpochId)>,
336}
337
338impl VcEmulationBindings {
339 pub fn get(&self, epoch: crate::group::GroupEpoch) -> Option<&EpochId> {
341 for (bound_epoch, epoch_id) in &self.bindings {
342 if *bound_epoch == epoch {
343 return Some(epoch_id);
344 }
345 }
346 None
347 }
348
349 pub(crate) fn insert(
352 &mut self,
353 epoch: crate::group::GroupEpoch,
354 epoch_id: EpochId,
355 max_entries: usize,
356 ) {
357 self.bindings
358 .retain(|(bound_epoch, _)| *bound_epoch != epoch);
359 self.bindings.push_back((epoch, epoch_id));
360 while self.bindings.len() > max_entries {
361 self.bindings.pop_front();
362 }
363 }
364}
365
366#[derive(Serialize, Deserialize)]
375pub(crate) struct EpochEncryptionKey(Secret);
376
377impl std::fmt::Debug for EpochEncryptionKey {
378 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379 f.debug_struct("EpochEncryptionKey")
380 .field("secret", &"<redacted>")
381 .finish()
382 }
383}
384
385#[derive(Debug, Serialize, Deserialize)]
394pub struct EmulationEpochState {
395 pub(crate) leaf_index: LeafNodeIndex,
399 pub(crate) epoch_encryption_key: EpochEncryptionKey,
400 pub(crate) reuse_guard_secret: ReuseGuardSecret,
401 pub(crate) emulation_group_size: TreeSize,
403 pub(crate) emulation_ciphersuite: Ciphersuite,
406}
407
408impl EmulationEpochState {
409 pub(crate) fn new(
410 leaf_index: LeafNodeIndex,
411 epoch_encryption_key: EpochEncryptionKey,
412 reuse_guard_secret: ReuseGuardSecret,
413 emulation_group_size: TreeSize,
414 emulation_ciphersuite: Ciphersuite,
415 ) -> Self {
416 Self {
417 leaf_index,
418 epoch_encryption_key,
419 reuse_guard_secret,
420 emulation_group_size,
421 emulation_ciphersuite,
422 }
423 }
424
425 pub(crate) fn into_parts(self) -> (LeafNodeIndex, EpochEncryptionKey, Ciphersuite) {
428 (
429 self.leaf_index,
430 self.epoch_encryption_key,
431 self.emulation_ciphersuite,
432 )
433 }
434
435 pub(crate) fn reuse_guard_inputs(&self) -> crate::framing::EmulatorReuseGuardCtx<'_> {
438 crate::framing::EmulatorReuseGuardCtx {
439 reuse_guard_secret: &self.reuse_guard_secret,
440 emulation_ciphersuite: self.emulation_ciphersuite,
441 emulation_group_size: self.emulation_group_size,
442 emulation_leaf_index: self.leaf_index,
443 }
444 }
445}
446
447#[derive(Serialize, Deserialize)]
451pub(crate) struct OperationSecret(Secret);
452
453impl From<Secret> for OperationSecret {
454 fn from(secret: Secret) -> Self {
455 Self(secret)
456 }
457}
458
459impl OperationSecret {
460 pub(crate) fn derive_encryption_key_secret(
461 &self,
462 crypto: &impl OpenMlsCrypto,
463 ciphersuite: Ciphersuite,
464 ) -> Result<EncryptionKeySecret, VirtualClientsError> {
465 let encryption_key_secret =
466 self.0
467 .derive_secret(crypto, ciphersuite, ENCRYPTION_KEY_LABEL)?;
468 Ok(EncryptionKeySecret(encryption_key_secret))
469 }
470
471 pub(crate) fn derive_path_generation_secret(
472 &self,
473 crypto: &impl OpenMlsCrypto,
474 ciphersuite: Ciphersuite,
475 ) -> Result<PathGenerationSecret, VirtualClientsError> {
476 let path_generation_secret =
477 self.0
478 .derive_secret(crypto, ciphersuite, PATH_GENERATION_LABEL)?;
479 Ok(PathGenerationSecret(path_generation_secret))
480 }
481}
482
483pub(crate) struct EncryptionKeySecret(Secret);
484
485impl EncryptionKeySecret {
486 pub(crate) fn generate_encryption_key_pair(
487 &self,
488 crypto: &impl OpenMlsCrypto,
489 ciphersuite: Ciphersuite,
490 ) -> Result<EncryptionKeyPair, VirtualClientsError> {
491 let hpke_config = ciphersuite.hpke_config();
492 let key_pair = crypto.derive_hpke_keypair(hpke_config, self.0.as_slice())?;
493 Ok(EncryptionKeyPair::from(key_pair))
494 }
495}
496
497pub(crate) struct PathGenerationSecret(Secret);
498
499impl From<PathGenerationSecret> for PathSecret {
500 fn from(value: PathGenerationSecret) -> Self {
501 value.0.into()
502 }
503}
504
505#[derive(Debug, TlsSize, TlsSerialize, TlsDeserializeBytes)]
506pub(crate) struct EncryptedEpochInfo {
507 nonce: Vec<u8>,
508 ciphertext: Vec<u8>,
509}
510
511impl EncryptedEpochInfo {
512 pub fn decrypt(
513 &self,
514 crypto: &impl OpenMlsCrypto,
515 ciphersuite: Ciphersuite,
516 key: &EpochEncryptionKey,
517 epoch_id: &EpochId,
518 ) -> Result<EpochInfoTbe, VirtualClientsError> {
519 let plaintext = crypto
520 .aead_decrypt(
521 ciphersuite.aead_algorithm(),
522 key.0.as_slice(),
523 self.ciphertext.as_slice(),
524 self.nonce.as_slice(),
525 epoch_id.0.as_slice(),
526 )
527 .map_err(|e| {
528 log::error!("vc: aead decrypt epoch info failed: {e:?}");
529 VirtualClientsError::EpochInfoDecryptionFailed
530 })?;
531 Ok(EpochInfoTbe::tls_deserialize_exact_bytes(&plaintext)?)
532 }
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
544#[repr(u8)]
545pub(crate) enum VirtualClientOperationType {
546 LeafNode = 1,
547 #[allow(dead_code)] KeyPackage = 2,
549 #[allow(dead_code)] Application = 3,
551}
552
553#[derive(Debug, PartialEq, Eq, TlsSize, TlsSerialize, TlsDeserializeBytes)]
560pub(crate) struct EpochInfoTbe {
561 pub operation_type: VirtualClientOperationType,
562 pub leaf_index: LeafNodeIndex,
563 pub random: Vec<u8>,
564}
565
566impl EpochInfoTbe {
567 pub fn encrypt(
568 &self,
569 crypto: &impl OpenMlsCrypto,
570 rand: &impl OpenMlsRand,
571 ciphersuite: Ciphersuite,
572 key: &EpochEncryptionKey,
573 epoch_id: &EpochId,
574 ) -> Result<EncryptedEpochInfo, VirtualClientsError> {
575 let nonce = rand
576 .random_vec(ciphersuite.aead_nonce_length())
577 .map_err(|e| {
578 log::error!("vc: aead nonce randomness failed: {e:?}");
579 VirtualClientsError::RandError
580 })?;
581 let payload = self.tls_serialize_detached()?;
582 let ciphertext = crypto.aead_encrypt(
583 ciphersuite.aead_algorithm(),
584 key.0.as_slice(),
585 payload.as_slice(),
586 nonce.as_slice(),
587 epoch_id.0.as_slice(),
588 )?;
589 Ok(EncryptedEpochInfo { nonce, ciphertext })
590 }
591}
592
593pub(crate) fn pprf_input(
597 crypto: &impl OpenMlsCrypto,
598 ciphersuite: Ciphersuite,
599 epoch_info: &EpochInfoTbe,
600) -> Result<[u8; Prefix256::PPRF_INPUT_LEN], VirtualClientsError> {
601 let serialized = epoch_info.tls_serialize_detached()?;
602 let hash = crypto.hash(ciphersuite.hash_algorithm(), &serialized)?;
603 if hash.len() < Prefix256::PPRF_INPUT_LEN {
604 log::error!(
605 "vc: pprf input hash too short: got {} bytes, need {}",
606 hash.len(),
607 Prefix256::PPRF_INPUT_LEN
608 );
609 return Err(VirtualClientsError::HashOutputLengthMismatch {
610 actual_length: hash.len(),
611 expected_length: Prefix256::PPRF_INPUT_LEN,
612 });
613 }
614 let mut input = [0u8; Prefix256::PPRF_INPUT_LEN];
615 input.copy_from_slice(&hash[..Prefix256::PPRF_INPUT_LEN]);
616 Ok(input)
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622 use openmls_rust_crypto::OpenMlsRustCrypto;
623 use openmls_traits::OpenMlsProvider;
624
625 #[test]
629 fn epoch_info_tbe_roundtrip() {
630 let provider = OpenMlsRustCrypto::default();
631 let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
632 let emulator = EmulatorEpochSecret::new(
633 &provider
634 .rand()
635 .random_vec(ciphersuite.hash_length())
636 .expect("randomness"),
637 );
638 let key = emulator
639 .derive_epoch_encryption_key(provider.crypto(), ciphersuite)
640 .expect("derive ek");
641 let epoch_id = emulator
642 .derive_epoch_id(provider.crypto(), ciphersuite)
643 .expect("derive epoch id");
644 let original = EpochInfoTbe {
645 operation_type: VirtualClientOperationType::LeafNode,
646 leaf_index: LeafNodeIndex::new(7),
647 random: provider.rand().random_vec(32).expect("randomness"),
648 };
649 let encrypted = original
650 .encrypt(
651 provider.crypto(),
652 provider.rand(),
653 ciphersuite,
654 &key,
655 &epoch_id,
656 )
657 .expect("encrypt");
658 let decrypted = encrypted
659 .decrypt(provider.crypto(), ciphersuite, &key, &epoch_id)
660 .expect("decrypt");
661 assert_eq!(original, decrypted);
662 }
663
664 #[test]
669 fn pprf_input_changes_with_operation_type() {
670 let provider = OpenMlsRustCrypto::default();
671 let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
672 let leaf_node = EpochInfoTbe {
673 operation_type: VirtualClientOperationType::LeafNode,
674 leaf_index: LeafNodeIndex::new(3),
675 random: vec![0xAA; 32],
676 };
677 let key_package = EpochInfoTbe {
678 operation_type: VirtualClientOperationType::KeyPackage,
679 leaf_index: LeafNodeIndex::new(3),
680 random: vec![0xAA; 32],
681 };
682 let application = EpochInfoTbe {
683 operation_type: VirtualClientOperationType::Application,
684 leaf_index: LeafNodeIndex::new(3),
685 random: vec![0xAA; 32],
686 };
687 let in_leaf = pprf_input(provider.crypto(), ciphersuite, &leaf_node).unwrap();
688 let in_kp = pprf_input(provider.crypto(), ciphersuite, &key_package).unwrap();
689 let in_app = pprf_input(provider.crypto(), ciphersuite, &application).unwrap();
690 assert_ne!(in_leaf, in_kp);
691 assert_ne!(in_leaf, in_app);
692 assert_ne!(in_kp, in_app);
693 }
694}