1use openmls_traits::{crypto::OpenMlsCrypto, types::*};
119use serde::{Deserialize, Serialize};
120use tls_codec::{TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize};
121
122use crate::{
123 binary_tree::array_representation::{LeafNodeIndex, TreeSize},
124 ciphersuite::{AeadKey, AeadNonce, HpkePrivateKey, Mac, Secret},
125 error::LibraryError,
126 framing::{mls_content::AuthenticatedContentTbm, MembershipTag},
127 group::GroupContext,
128 messages::{ConfirmationTag, PathSecret},
129 tree::secret_tree::SecretTree,
130 versions::ProtocolVersion,
131};
132
133pub mod errors;
135#[cfg(feature = "extensions-draft")]
136pub(crate) mod pprf;
137pub mod psk;
138
139#[cfg(feature = "extensions-draft")]
140pub use pprf::PprfError;
141
142#[cfg(feature = "extensions-draft")]
144pub(crate) mod application_export_tree;
145pub(crate) mod message_secrets;
146
147use errors::*;
149use message_secrets::MessageSecrets;
150use openmls_traits::random::OpenMlsRand;
151use psk::PskSecret;
152
153#[cfg(any(feature = "test-utils", test))]
155pub mod tests_and_kats;
156
157pub use psk::{ExternalPsk, PreSharedKeyId, Psk};
159
160#[derive(Clone, Debug, Serialize, Deserialize)]
163#[cfg_attr(any(test, feature = "test-utils"), derive(Eq, PartialEq))]
164pub struct ResumptionPskSecret {
165 secret: Secret,
166}
167
168impl ResumptionPskSecret {
169 fn new(
171 crypto: &impl OpenMlsCrypto,
172 ciphersuite: Ciphersuite,
173 epoch_secret: &EpochSecret,
174 ) -> Result<Self, CryptoError> {
175 let secret = epoch_secret
176 .secret
177 .derive_secret(crypto, ciphersuite, "resumption")?;
178 Ok(Self { secret })
179 }
180
181 pub fn as_slice(&self) -> &[u8] {
183 self.secret.as_slice()
184 }
185}
186
187#[derive(Debug, Serialize, Deserialize)]
190#[cfg_attr(any(test, feature = "test-utils"), derive(Eq, PartialEq, Clone))]
191pub struct EpochAuthenticator {
192 secret: Secret,
193}
194
195impl EpochAuthenticator {
196 fn new(
198 crypto: &impl OpenMlsCrypto,
199 ciphersuite: Ciphersuite,
200 epoch_secret: &EpochSecret,
201 ) -> Result<Self, CryptoError> {
202 let secret = epoch_secret
203 .secret
204 .derive_secret(crypto, ciphersuite, "authentication")?;
205 Ok(Self { secret })
206 }
207
208 pub fn as_slice(&self) -> &[u8] {
210 self.secret.as_slice()
211 }
212}
213
214#[derive(Debug, Default, Serialize, Deserialize)]
217#[cfg_attr(test, derive(PartialEq))]
218#[cfg_attr(any(feature = "test-utils", test), derive(Clone))]
219pub(crate) struct CommitSecret {
220 secret: Secret,
221}
222
223impl From<PathSecret> for CommitSecret {
224 fn from(path_secret: PathSecret) -> Self {
225 CommitSecret {
226 secret: path_secret.secret(),
227 }
228 }
229}
230
231impl CommitSecret {
232 pub(crate) fn zero_secret(ciphersuite: Ciphersuite) -> Self {
235 CommitSecret {
236 secret: Secret::zero(ciphersuite),
237 }
238 }
239
240 #[cfg(any(feature = "test-utils", test))]
241 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
242 Self {
243 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
244 }
245 }
246
247 #[cfg(any(feature = "test-utils", test))]
248 pub(crate) fn as_slice(&self) -> &[u8] {
249 self.secret.as_slice()
250 }
251}
252
253#[derive(Debug, Serialize, Deserialize)]
255#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
256pub(crate) struct InitSecret {
257 secret: Secret,
258}
259
260impl From<Secret> for InitSecret {
261 fn from(secret: Secret) -> Self {
262 Self { secret }
263 }
264}
265
266fn hpke_info_from_version(version: ProtocolVersion) -> &'static str {
270 match version {
271 ProtocolVersion::Mls10 => "MLS 1.0 external init secret",
272 _ => "<OpenMLS reserved; Don't use this.>",
273 }
274}
275
276impl InitSecret {
277 fn new(
279 crypto: &impl OpenMlsCrypto,
280 ciphersuite: Ciphersuite,
281 epoch_secret: EpochSecret,
282 ) -> Result<Self, CryptoError> {
283 let secret = epoch_secret
284 .secret
285 .derive_secret(crypto, ciphersuite, "init")?;
286 log_crypto!(trace, "Init secret: {:x?}", secret);
287 Ok(InitSecret { secret })
288 }
289
290 pub(crate) fn random(
292 ciphersuite: Ciphersuite,
293 rand: &impl OpenMlsRand,
294 ) -> Result<Self, CryptoError> {
295 Ok(InitSecret {
296 secret: Secret::random(ciphersuite, rand)?,
297 })
298 }
299
300 pub(crate) fn from_group_context(
302 crypto: &impl OpenMlsCrypto,
303 group_context: &GroupContext,
304 external_pub: &[u8],
305 ) -> Result<(Self, Vec<u8>), KeyScheduleError> {
306 let ciphersuite = group_context.ciphersuite();
307 let version = group_context.protocol_version();
308 let (kem_output, raw_init_secret) = crypto.hpke_setup_sender_and_export(
309 ciphersuite.hpke_config(),
310 external_pub,
311 &[],
312 hpke_info_from_version(version).as_bytes(),
313 ciphersuite.hash_length(),
314 )?;
315 Ok((
316 InitSecret {
317 secret: Secret::from_slice(&raw_init_secret),
318 },
319 kem_output,
320 ))
321 }
322
323 pub(crate) fn from_kem_output(
325 crypto: &impl OpenMlsCrypto,
326 ciphersuite: Ciphersuite,
327 version: ProtocolVersion,
328 external_priv: &HpkePrivateKey,
329 kem_output: &[u8],
330 ) -> Result<Self, LibraryError> {
331 let raw_init_secret = crypto
332 .hpke_setup_receiver_and_export(
333 ciphersuite.hpke_config(),
334 kem_output,
335 external_priv,
336 &[],
337 hpke_info_from_version(version).as_bytes(),
338 ciphersuite.hash_length(),
339 )
340 .map_err(LibraryError::unexpected_crypto_error)?;
341 Ok(InitSecret {
342 secret: Secret::from_slice(&raw_init_secret),
343 })
344 }
345
346 #[cfg(any(feature = "test-utils", test))]
347 pub(crate) fn clone(&self) -> Self {
348 Self {
349 secret: self.secret.clone(),
350 }
351 }
352
353 #[cfg(any(feature = "test-utils", test, feature = "virtual-clients-draft"))]
354 pub(crate) fn as_slice(&self) -> &[u8] {
355 self.secret.as_slice()
356 }
357}
358
359#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize)]
360pub(crate) struct JoinerSecret {
361 secret: Secret,
362}
363
364impl JoinerSecret {
365 pub(crate) fn new(
370 crypto: &impl OpenMlsCrypto,
371 ciphersuite: Ciphersuite,
372 commit_secret_option: impl Into<Option<CommitSecret>>,
373 init_secret: &InitSecret,
374 serialized_group_context: &[u8],
375 ) -> Result<Self, CryptoError> {
376 let intermediate_secret = init_secret.secret.hkdf_extract(
377 crypto,
378 ciphersuite,
379 commit_secret_option.into().as_ref().map(|cs| &cs.secret),
380 )?;
381 let secret = intermediate_secret.kdf_expand_label(
382 crypto,
383 ciphersuite,
384 "joiner",
385 serialized_group_context,
386 ciphersuite.hash_length(),
387 )?;
388 log_crypto!(trace, "Joiner secret: {:x?}", secret);
389 Ok(JoinerSecret { secret })
390 }
391
392 #[cfg(any(feature = "test-utils", test))]
393 pub(crate) fn as_slice(&self) -> &[u8] {
394 self.secret.as_slice()
395 }
396
397 #[cfg(test)]
398 pub(crate) fn random(ciphersuite: Ciphersuite, rand: &impl OpenMlsRand) -> Self {
399 Self {
400 secret: Secret::random(ciphersuite, rand).expect("Not enough randomness."),
401 }
402 }
403}
404
405#[derive(Debug, PartialEq)]
407enum State {
408 Initial,
409 Context,
410 Done,
411}
412
413pub(crate) struct KeySchedule {
414 ciphersuite: Ciphersuite,
415 intermediate_secret: Option<IntermediateSecret>,
416 epoch_secret: Option<EpochSecret>,
417 state: State,
418}
419
420pub(crate) struct EpochSecretsResult {
421 pub(crate) epoch_secrets: EpochSecrets,
422 #[cfg(feature = "extensions-draft")]
423 pub(crate) application_exporter: ApplicationExportSecret,
424}
425
426impl KeySchedule {
427 pub(crate) fn init(
429 ciphersuite: Ciphersuite,
430 crypto: &impl OpenMlsCrypto,
431 joiner_secret: &JoinerSecret,
432 psk: PskSecret,
433 ) -> Result<Self, LibraryError> {
434 log::debug!("Initializing the key schedule with {ciphersuite:?} ...");
435 log_crypto!(
436 trace,
437 " joiner_secret: {:x?}",
438 joiner_secret.secret.as_slice()
439 );
440 let intermediate_secret = IntermediateSecret::new(crypto, ciphersuite, joiner_secret, psk)
441 .map_err(LibraryError::unexpected_crypto_error)?;
442 Ok(Self {
443 ciphersuite,
444 intermediate_secret: Some(intermediate_secret),
445 epoch_secret: None,
446 state: State::Initial,
447 })
448 }
449
450 pub(crate) fn welcome(
453 &self,
454 crypto: &impl OpenMlsCrypto,
455 ciphersuite: Ciphersuite,
456 ) -> Result<WelcomeSecret, KeyScheduleError> {
457 if self.state != State::Initial || self.intermediate_secret.is_none() {
458 log::error!("Trying to derive a welcome secret while not in the initial state.");
459 return Err(KeyScheduleError::InvalidState(ErrorState::Init));
460 }
461
462 let intermediate_secret = self
464 .intermediate_secret
465 .as_ref()
466 .ok_or_else(|| LibraryError::custom("state machine error"))?;
467
468 Ok(WelcomeSecret::new(
469 crypto,
470 ciphersuite,
471 intermediate_secret,
472 )?)
473 }
474
475 pub(crate) fn add_context(
477 &mut self,
478 crypto: &impl OpenMlsCrypto,
479 serialized_group_context: &[u8],
480 ) -> Result<(), KeyScheduleError> {
481 log::trace!("Adding context to key schedule. {serialized_group_context:?}");
482 if self.state != State::Initial || self.intermediate_secret.is_none() {
483 log::error!(
484 "Trying to add context to the key schedule while not in the initial state."
485 );
486 return Err(KeyScheduleError::InvalidState(ErrorState::Init));
487 }
488 self.state = State::Context;
489
490 let intermediate_secret = self
492 .intermediate_secret
493 .take()
494 .ok_or_else(|| LibraryError::custom("state machine error"))?;
495
496 log_crypto!(
497 trace,
498 " intermediate_secret: {:x?}",
499 intermediate_secret.secret.as_slice()
500 );
501
502 self.epoch_secret = Some(EpochSecret::new(
503 self.ciphersuite,
504 crypto,
505 intermediate_secret,
506 serialized_group_context,
507 )?);
508 self.intermediate_secret = None;
509 Ok(())
510 }
511
512 pub(crate) fn epoch_secrets(
516 &mut self,
517 crypto: &impl OpenMlsCrypto,
518 ciphersuite: Ciphersuite,
519 ) -> Result<EpochSecretsResult, KeyScheduleError> {
520 if self.state != State::Context || self.epoch_secret.is_none() {
521 log::error!("Trying to derive the epoch secrets while not in the right state.");
522 return Err(KeyScheduleError::InvalidState(ErrorState::Context));
523 }
524 self.state = State::Done;
525
526 let epoch_secret = match self.epoch_secret.take() {
527 Some(epoch_secret) => epoch_secret,
528 None => return Err(LibraryError::custom("state machine error").into()),
530 };
531
532 let res = EpochSecretsResult {
533 #[cfg(feature = "extensions-draft")]
534 application_exporter: ApplicationExportSecret::new(crypto, ciphersuite, &epoch_secret)?,
535 epoch_secrets: EpochSecrets::new(crypto, ciphersuite, epoch_secret)?,
536 };
537
538 Ok(res)
539 }
540}
541
542struct IntermediateSecret {
545 secret: Secret,
546}
547
548impl IntermediateSecret {
549 fn new(
552 crypto: &impl OpenMlsCrypto,
553 ciphersuite: Ciphersuite,
554 joiner_secret: &JoinerSecret,
555 psk: PskSecret,
556 ) -> Result<Self, CryptoError> {
557 log_crypto!(trace, "PSK input: {:x?}", psk.as_slice());
558 let secret = joiner_secret
559 .secret
560 .hkdf_extract(crypto, ciphersuite, psk.secret())?;
561 log_crypto!(trace, "Intermediate secret: {:x?}", secret);
562 Ok(Self { secret })
563 }
564}
565
566pub(crate) struct WelcomeSecret {
567 secret: Secret,
568}
569
570impl WelcomeSecret {
571 fn new(
573 crypto: &impl OpenMlsCrypto,
574 ciphersuite: Ciphersuite,
575 intermediate_secret: &IntermediateSecret,
576 ) -> Result<Self, CryptoError> {
577 let secret = intermediate_secret
578 .secret
579 .derive_secret(crypto, ciphersuite, "welcome")?;
580 log_crypto!(trace, "Welcome secret: {:x?}", secret);
581 Ok(WelcomeSecret { secret })
582 }
583
584 pub(crate) fn derive_welcome_key_nonce(
587 self,
588 crypto: &impl OpenMlsCrypto,
589 ciphersuite: Ciphersuite,
590 ) -> Result<(AeadKey, AeadNonce), CryptoError> {
591 let welcome_nonce = self.derive_aead_nonce(crypto, ciphersuite)?;
592 let welcome_key = self.derive_aead_key(crypto, ciphersuite)?;
593 Ok((welcome_key, welcome_nonce))
594 }
595
596 fn derive_aead_key(
598 &self,
599 crypto: &impl OpenMlsCrypto,
600 ciphersuite: Ciphersuite,
601 ) -> Result<AeadKey, CryptoError> {
602 log::trace!("WelcomeSecret.derive_aead_key with {ciphersuite}");
603 let aead_secret = self.secret.kdf_expand_label(
604 crypto,
605 ciphersuite,
606 "key",
607 b"",
608 ciphersuite.aead_key_length(),
609 )?;
610 Ok(AeadKey::from_secret(aead_secret, ciphersuite))
611 }
612
613 fn derive_aead_nonce(
615 &self,
616 crypto: &impl OpenMlsCrypto,
617 ciphersuite: Ciphersuite,
618 ) -> Result<AeadNonce, CryptoError> {
619 let nonce_secret = self.secret.kdf_expand_label(
620 crypto,
621 ciphersuite,
622 "nonce",
623 b"",
624 ciphersuite.aead_nonce_length(),
625 )?;
626 Ok(AeadNonce::from_secret(nonce_secret))
627 }
628
629 #[cfg(any(feature = "test-utils", test))]
630 pub(crate) fn as_slice(&self) -> &[u8] {
631 self.secret.as_slice()
632 }
633}
634
635struct EpochSecret {
639 secret: Secret,
640}
641
642impl EpochSecret {
643 fn new(
645 ciphersuite: Ciphersuite,
646 crypto: &impl OpenMlsCrypto,
647 intermediate_secret: IntermediateSecret,
648 serialized_group_context: &[u8],
649 ) -> Result<Self, CryptoError> {
650 let secret = intermediate_secret.secret.kdf_expand_label(
651 crypto,
652 ciphersuite,
653 "epoch",
654 serialized_group_context,
655 ciphersuite.hash_length(),
656 )?;
657 log_crypto!(trace, "Epoch secret: {:x?}", secret);
658 Ok(EpochSecret { secret })
659 }
660}
661
662#[cfg_attr(test, derive(Clone))]
664pub(crate) struct EncryptionSecret {
665 secret: Secret,
666}
667
668impl EncryptionSecret {
669 fn new(
671 crypto: &impl OpenMlsCrypto,
672 ciphersuite: Ciphersuite,
673 epoch_secret: &EpochSecret,
674 ) -> Result<Self, CryptoError> {
675 Ok(EncryptionSecret {
676 secret: epoch_secret
677 .secret
678 .derive_secret(crypto, ciphersuite, "encryption")?,
679 })
680 }
681
682 pub(crate) fn create_secret_tree(
685 self,
686 treesize: TreeSize,
687 own_index: LeafNodeIndex,
688 ) -> SecretTree {
689 SecretTree::new(self, treesize, own_index)
690 }
691
692 pub(crate) fn consume_secret(self) -> Secret {
693 self.secret
694 }
695
696 #[cfg(test)]
698 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
699 EncryptionSecret {
700 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
701 }
702 }
703
704 #[cfg(any(feature = "test-utils", test))]
705 pub(crate) fn as_slice(&self) -> &[u8] {
706 self.secret.as_slice()
707 }
708
709 #[cfg(any(feature = "test-utils", test))]
710 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
712 Self {
713 secret: Secret::from_slice(bytes),
714 }
715 }
716}
717
718#[derive(Debug, Serialize, Deserialize)]
720#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
721pub(crate) struct ExporterSecret {
722 secret: Secret,
723}
724
725impl ExporterSecret {
726 fn new(
728 crypto: &impl OpenMlsCrypto,
729 ciphersuite: Ciphersuite,
730 epoch_secret: &EpochSecret,
731 ) -> Result<Self, CryptoError> {
732 let secret = epoch_secret
733 .secret
734 .derive_secret(crypto, ciphersuite, "exporter")?;
735 Ok(ExporterSecret { secret })
736 }
737
738 #[cfg(any(feature = "test-utils", test))]
739 pub(crate) fn as_slice(&self) -> &[u8] {
740 self.secret.as_slice()
741 }
742
743 #[cfg(all(feature = "targeted-messages-draft", test))]
745 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
746 Self {
747 secret: Secret::from_slice(bytes),
748 }
749 }
750
751 pub(crate) fn derive_exported_secret(
754 &self,
755 ciphersuite: Ciphersuite,
756 crypto: &impl OpenMlsCrypto,
757 label: &str,
758 context: &[u8],
759 key_length: usize,
760 ) -> Result<Secret, CryptoError> {
761 let context_hash = &crypto.hash(ciphersuite.hash_algorithm(), context)?;
762 self.secret
763 .derive_secret(crypto, ciphersuite, label)?
764 .kdf_expand_label(crypto, ciphersuite, "exported", context_hash, key_length)
765 }
766}
767
768#[cfg(feature = "extensions-draft")]
772#[derive(Debug, Serialize, Deserialize, Clone)]
773#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq))]
774pub struct ApplicationExportSecret {
775 secret: Secret,
776}
777
778#[cfg(feature = "extensions-draft")]
779impl ApplicationExportSecret {
780 fn new(
782 crypto: &impl OpenMlsCrypto,
783 ciphersuite: Ciphersuite,
784 epoch_secret: &EpochSecret,
785 ) -> Result<Self, CryptoError> {
786 let secret =
787 epoch_secret
788 .secret
789 .derive_secret(crypto, ciphersuite, "application_export")?;
790 Ok(ApplicationExportSecret { secret })
791 }
792}
793
794#[derive(Debug, Serialize, Deserialize)]
796#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
797pub(crate) struct ExternalSecret {
798 secret: Secret,
799}
800
801impl ExternalSecret {
802 fn new(
804 crypto: &impl OpenMlsCrypto,
805 ciphersuite: Ciphersuite,
806 epoch_secret: &EpochSecret,
807 ) -> Result<Self, CryptoError> {
808 let secret = epoch_secret
809 .secret
810 .derive_secret(crypto, ciphersuite, "external")?;
811 Ok(Self { secret })
812 }
813
814 pub(crate) fn derive_external_keypair(
816 &self,
817 crypto: &impl OpenMlsCrypto,
818 ciphersuite: Ciphersuite,
819 ) -> Result<HpkeKeyPair, CryptoError> {
820 crypto.derive_hpke_keypair(ciphersuite.hpke_config(), self.secret.as_slice())
821 }
822
823 #[cfg(any(feature = "test-utils", test))]
824 pub(crate) fn as_slice(&self) -> &[u8] {
825 self.secret.as_slice()
826 }
827}
828
829#[derive(Debug, Serialize, Deserialize)]
831#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
832pub(crate) struct ConfirmationKey {
833 secret: Secret,
834}
835
836impl ConfirmationKey {
837 fn new(
839 crypto: &impl OpenMlsCrypto,
840 ciphersuite: Ciphersuite,
841 epoch_secret: &EpochSecret,
842 ) -> Result<Self, CryptoError> {
843 log::debug!("Computing confirmation key.");
844 log_crypto!(
845 trace,
846 " epoch_secret {:x?}",
847 epoch_secret.secret.as_slice()
848 );
849 let secret = epoch_secret
850 .secret
851 .derive_secret(crypto, ciphersuite, "confirm")?;
852 Ok(Self { secret })
853 }
854
855 pub(crate) fn tag(
864 &self,
865 crypto: &impl OpenMlsCrypto,
866 ciphersuite: Ciphersuite,
867 confirmed_transcript_hash: &[u8],
868 ) -> Result<ConfirmationTag, CryptoError> {
869 log::debug!("Computing confirmation tag.");
870 log_crypto!(trace, " confirmation key {:x?}", self.secret.as_slice());
871 log_crypto!(trace, " transcript hash {:x?}", confirmed_transcript_hash);
872 Ok(ConfirmationTag(Mac::new(
873 crypto,
874 ciphersuite,
875 &self.secret,
876 confirmed_transcript_hash,
877 )?))
878 }
879}
880
881#[cfg(test)]
882impl ConfirmationKey {
883 pub(crate) fn from_secret(secret: Secret) -> Self {
884 Self { secret }
885 }
886}
887
888#[cfg(any(feature = "test-utils", test))]
889impl ConfirmationKey {
890 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
891 Self {
892 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
893 }
894 }
895
896 pub(crate) fn as_slice(&self) -> &[u8] {
897 self.secret.as_slice()
898 }
899}
900
901#[derive(Debug, Serialize, Deserialize)]
903#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
904pub(crate) struct MembershipKey {
905 secret: Secret,
906}
907
908impl MembershipKey {
909 fn new(
911 crypto: &impl OpenMlsCrypto,
912 ciphersuite: Ciphersuite,
913 epoch_secret: &EpochSecret,
914 ) -> Result<Self, CryptoError> {
915 let secret = epoch_secret
916 .secret
917 .derive_secret(crypto, ciphersuite, "membership")?;
918 Ok(Self { secret })
919 }
920
921 pub(crate) fn tag_message(
929 &self,
930 crypto: &impl OpenMlsCrypto,
931 ciphersuite: Ciphersuite,
932 tbm_payload: AuthenticatedContentTbm,
933 ) -> Result<MembershipTag, LibraryError> {
934 Ok(MembershipTag(
935 Mac::new(
936 crypto,
937 ciphersuite,
938 &self.secret,
939 &tbm_payload
940 .into_bytes()
941 .map_err(LibraryError::missing_bound_check)?,
942 )
943 .map_err(LibraryError::unexpected_crypto_error)?,
944 ))
945 }
946
947 #[cfg(any(feature = "test-utils", test))]
948 pub(crate) fn from_secret(secret: Secret) -> Self {
949 Self { secret }
950 }
951
952 #[cfg(any(feature = "test-utils", test))]
953 pub(crate) fn as_slice(&self) -> &[u8] {
954 self.secret.as_slice()
955 }
956
957 #[cfg(any(feature = "test-utils", test))]
958 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
959 Self {
960 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
961 }
962 }
963}
964
965fn ciphertext_sample(ciphersuite: Ciphersuite, ciphertext: &[u8]) -> &[u8] {
967 let sample_length = ciphersuite.hash_length();
968 log::debug!("Getting ciphertext sample of length {sample_length:?}");
969 if ciphertext.len() <= sample_length {
970 ciphertext
971 } else {
972 &ciphertext[0..sample_length]
973 }
974}
975
976#[derive(Serialize, Deserialize)]
978#[cfg_attr(
979 any(feature = "test-utils", feature = "crypto-debug", test),
980 derive(Debug, Clone, PartialEq)
981)]
982pub(crate) struct SenderDataSecret {
983 secret: Secret,
984}
985
986impl SenderDataSecret {
987 fn new(
989 crypto: &impl OpenMlsCrypto,
990 ciphersuite: Ciphersuite,
991 epoch_secret: &EpochSecret,
992 ) -> Result<Self, CryptoError> {
993 let secret = epoch_secret
994 .secret
995 .derive_secret(crypto, ciphersuite, "sender data")?;
996 Ok(SenderDataSecret { secret })
997 }
998
999 pub(crate) fn derive_aead_key(
1001 &self,
1002 crypto: &impl OpenMlsCrypto,
1003 ciphersuite: Ciphersuite,
1004 ciphertext: &[u8],
1005 ) -> Result<AeadKey, CryptoError> {
1006 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
1007 log::debug!("SenderDataSecret::derive_aead_key ciphertext sample: {ciphertext_sample:x?}");
1008 let secret = self.secret.kdf_expand_label(
1009 crypto,
1010 ciphersuite,
1011 "key",
1012 ciphertext_sample,
1013 ciphersuite.aead_key_length(),
1014 )?;
1015 Ok(AeadKey::from_secret(secret, ciphersuite))
1016 }
1017
1018 pub(crate) fn derive_aead_nonce(
1020 &self,
1021 ciphersuite: Ciphersuite,
1022 crypto: &impl OpenMlsCrypto,
1023 ciphertext: &[u8],
1024 ) -> Result<AeadNonce, CryptoError> {
1025 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
1026 log::debug!(
1027 "SenderDataSecret::derive_aead_nonce ciphertext sample: {ciphertext_sample:x?}"
1028 );
1029 let nonce_secret = self.secret.kdf_expand_label(
1030 crypto,
1031 ciphersuite,
1032 "nonce",
1033 ciphertext_sample,
1034 ciphersuite.aead_nonce_length(),
1035 )?;
1036 Ok(AeadNonce::from_secret(nonce_secret))
1037 }
1038
1039 #[cfg(any(feature = "test-utils", test))]
1040 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
1041 Self {
1042 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
1043 }
1044 }
1045
1046 #[cfg(any(feature = "test-utils", test))]
1047 pub(crate) fn as_slice(&self) -> &[u8] {
1048 self.secret.as_slice()
1049 }
1050
1051 #[cfg(any(feature = "test-utils", test))]
1052 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1054 Self {
1055 secret: Secret::from_slice(bytes),
1056 }
1057 }
1058}
1059
1060pub(crate) struct EpochSecrets {
1076 init_secret: InitSecret,
1077 sender_data_secret: SenderDataSecret,
1078 encryption_secret: EncryptionSecret,
1079 exporter_secret: ExporterSecret,
1080 epoch_authenticator: EpochAuthenticator,
1081 external_secret: ExternalSecret,
1082 confirmation_key: ConfirmationKey,
1083 membership_key: MembershipKey,
1084 resumption_psk: ResumptionPskSecret,
1085}
1086
1087impl std::fmt::Debug for EpochSecrets {
1088 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1089 f.write_str("EpochSecrets { *** }")
1090 }
1091}
1092
1093#[cfg(not(test))]
1094impl PartialEq for EpochSecrets {
1095 fn eq(&self, _other: &Self) -> bool {
1096 false
1097 }
1098}
1099
1100#[cfg(test)]
1102impl PartialEq for EpochSecrets {
1103 fn eq(&self, other: &Self) -> bool {
1104 self.sender_data_secret == other.sender_data_secret
1105 && self.exporter_secret == other.exporter_secret
1106 && self.epoch_authenticator == other.epoch_authenticator
1107 && self.external_secret == other.external_secret
1108 && self.confirmation_key == other.confirmation_key
1109 && self.membership_key == other.membership_key
1110 && self.resumption_psk == other.resumption_psk
1111 }
1112}
1113
1114impl EpochSecrets {
1115 #[cfg(any(feature = "test-utils", test))]
1117 pub(crate) fn sender_data_secret(&self) -> &SenderDataSecret {
1118 &self.sender_data_secret
1119 }
1120
1121 pub(crate) fn confirmation_key(&self) -> &ConfirmationKey {
1123 &self.confirmation_key
1124 }
1125
1126 #[cfg(any(feature = "test-utils", test))]
1128 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1129 &self.epoch_authenticator
1130 }
1131
1132 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1134 &self.exporter_secret
1135 }
1136
1137 #[cfg(any(feature = "test-utils", test))]
1139 pub(crate) fn membership_key(&self) -> &MembershipKey {
1140 &self.membership_key
1141 }
1142
1143 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1145 &self.external_secret
1146 }
1147
1148 #[cfg(any(feature = "test-utils", test))]
1150 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1151 &self.resumption_psk
1152 }
1153
1154 #[cfg(any(feature = "test-utils", test))]
1156 pub(crate) fn init_secret(&self) -> &InitSecret {
1157 &self.init_secret
1158 }
1159
1160 #[cfg(any(feature = "test-utils", test))]
1162 pub(crate) fn encryption_secret(&self) -> &EncryptionSecret {
1163 &self.encryption_secret
1164 }
1165
1166 fn new(
1170 crypto: &impl OpenMlsCrypto,
1171 ciphersuite: Ciphersuite,
1172 epoch_secret: EpochSecret,
1173 ) -> Result<Self, CryptoError> {
1174 log::debug!("Computing EpochSecrets from epoch secret with {ciphersuite}");
1175 log_crypto!(
1176 trace,
1177 " epoch_secret: {:x?}",
1178 epoch_secret.secret.as_slice()
1179 );
1180 let sender_data_secret = SenderDataSecret::new(crypto, ciphersuite, &epoch_secret)?;
1181 let encryption_secret = EncryptionSecret::new(crypto, ciphersuite, &epoch_secret)?;
1182 let exporter_secret = ExporterSecret::new(crypto, ciphersuite, &epoch_secret)?;
1183 let epoch_authenticator = EpochAuthenticator::new(crypto, ciphersuite, &epoch_secret)?;
1184 let external_secret = ExternalSecret::new(crypto, ciphersuite, &epoch_secret)?;
1185 let confirmation_key = ConfirmationKey::new(crypto, ciphersuite, &epoch_secret)?;
1186 let membership_key = MembershipKey::new(crypto, ciphersuite, &epoch_secret)?;
1187 let resumption_psk = ResumptionPskSecret::new(crypto, ciphersuite, &epoch_secret)?;
1188
1189 log::trace!(" Computing init secret.");
1190 let init_secret = InitSecret::new(crypto, ciphersuite, epoch_secret)?;
1191
1192 Ok(EpochSecrets {
1193 init_secret,
1194 sender_data_secret,
1195 encryption_secret,
1196 exporter_secret,
1197 epoch_authenticator,
1198 external_secret,
1199 confirmation_key,
1200 membership_key,
1201 resumption_psk,
1202 })
1203 }
1204
1205 pub(crate) fn with_init_secret(
1210 crypto: &impl OpenMlsCrypto,
1211 ciphersuite: Ciphersuite,
1212 init_secret: InitSecret,
1213 ) -> Result<Self, CryptoError> {
1214 let epoch_secret = EpochSecret {
1215 secret: Secret::zero(ciphersuite),
1216 };
1217 let mut epoch_secrets = Self::new(crypto, ciphersuite, epoch_secret)?;
1218 epoch_secrets.init_secret = init_secret;
1219 Ok(epoch_secrets)
1220 }
1221
1222 #[cfg(feature = "virtual-clients-draft")]
1229 pub(crate) fn from_epoch_secret(
1230 crypto: &impl OpenMlsCrypto,
1231 ciphersuite: Ciphersuite,
1232 epoch_secret: Secret,
1233 ) -> Result<Self, CryptoError> {
1234 let epoch_secret = EpochSecret {
1235 secret: epoch_secret,
1236 };
1237 Self::new(crypto, ciphersuite, epoch_secret)
1238 }
1239
1240 pub(crate) fn split_secrets(
1245 self,
1246 serialized_context: Vec<u8>,
1247 treesize: TreeSize,
1248 own_index: LeafNodeIndex,
1249 ) -> (GroupEpochSecrets, MessageSecrets) {
1250 let secret_tree = self
1251 .encryption_secret
1252 .create_secret_tree(treesize, own_index);
1253 (
1254 GroupEpochSecrets {
1255 init_secret: self.init_secret,
1256 exporter_secret: self.exporter_secret,
1257 epoch_authenticator: self.epoch_authenticator,
1258 external_secret: self.external_secret,
1259 resumption_psk: self.resumption_psk,
1260 },
1261 MessageSecrets::new(
1262 self.sender_data_secret,
1263 self.membership_key,
1264 self.confirmation_key,
1265 serialized_context,
1266 secret_tree,
1267 ),
1268 )
1269 }
1270}
1271
1272#[derive(Serialize, Deserialize)]
1273#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
1274pub(crate) struct GroupEpochSecrets {
1275 init_secret: InitSecret,
1276 exporter_secret: ExporterSecret,
1277 epoch_authenticator: EpochAuthenticator,
1278 external_secret: ExternalSecret,
1279 resumption_psk: ResumptionPskSecret,
1280}
1281
1282impl std::fmt::Debug for GroupEpochSecrets {
1283 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1284 f.write_str("GroupEpochSecrets { *** }")
1285 }
1286}
1287
1288#[cfg(not(any(test, feature = "test-utils")))]
1289impl PartialEq for GroupEpochSecrets {
1290 fn eq(&self, _other: &Self) -> bool {
1291 false
1292 }
1293}
1294
1295impl GroupEpochSecrets {
1296 pub(crate) fn init_secret(&self) -> &InitSecret {
1298 &self.init_secret
1299 }
1300
1301 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1303 &self.epoch_authenticator
1304 }
1305
1306 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1308 &self.exporter_secret
1309 }
1310
1311 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1313 &self.external_secret
1314 }
1315
1316 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1318 &self.resumption_psk
1319 }
1320}