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;
135pub mod psk;
136
137pub(crate) mod message_secrets;
139
140use errors::*;
142use message_secrets::MessageSecrets;
143use openmls_traits::random::OpenMlsRand;
144use psk::PskSecret;
145
146#[cfg(any(feature = "test-utils", test))]
148pub mod tests_and_kats;
149
150pub use psk::{ExternalPsk, PreSharedKeyId, Psk};
152
153#[derive(Clone, Debug, Serialize, Deserialize)]
156#[cfg_attr(any(test, feature = "test-utils"), derive(Eq, PartialEq))]
157pub struct ResumptionPskSecret {
158 secret: Secret,
159}
160
161impl ResumptionPskSecret {
162 fn new(
164 crypto: &impl OpenMlsCrypto,
165 ciphersuite: Ciphersuite,
166 epoch_secret: &EpochSecret,
167 ) -> Result<Self, CryptoError> {
168 let secret = epoch_secret
169 .secret
170 .derive_secret(crypto, ciphersuite, "resumption")?;
171 Ok(Self { secret })
172 }
173
174 pub fn as_slice(&self) -> &[u8] {
176 self.secret.as_slice()
177 }
178}
179
180#[derive(Debug, Serialize, Deserialize)]
183#[cfg_attr(any(test, feature = "test-utils"), derive(Eq, PartialEq, Clone))]
184pub struct EpochAuthenticator {
185 secret: Secret,
186}
187
188impl EpochAuthenticator {
189 fn new(
191 crypto: &impl OpenMlsCrypto,
192 ciphersuite: Ciphersuite,
193 epoch_secret: &EpochSecret,
194 ) -> Result<Self, CryptoError> {
195 let secret = epoch_secret
196 .secret
197 .derive_secret(crypto, ciphersuite, "authentication")?;
198 Ok(Self { secret })
199 }
200
201 pub fn as_slice(&self) -> &[u8] {
203 self.secret.as_slice()
204 }
205}
206
207#[derive(Debug, Default, Serialize, Deserialize)]
210#[cfg_attr(test, derive(PartialEq))]
211#[cfg_attr(any(feature = "test-utils", test), derive(Clone))]
212pub(crate) struct CommitSecret {
213 secret: Secret,
214}
215
216impl From<PathSecret> for CommitSecret {
217 fn from(path_secret: PathSecret) -> Self {
218 CommitSecret {
219 secret: path_secret.secret(),
220 }
221 }
222}
223
224impl CommitSecret {
225 pub(crate) fn zero_secret(ciphersuite: Ciphersuite) -> Self {
228 CommitSecret {
229 secret: Secret::zero(ciphersuite),
230 }
231 }
232
233 #[cfg(any(feature = "test-utils", test))]
234 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
235 Self {
236 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
237 }
238 }
239
240 #[cfg(any(feature = "test-utils", test))]
241 pub(crate) fn as_slice(&self) -> &[u8] {
242 self.secret.as_slice()
243 }
244}
245
246#[derive(Debug, Serialize, Deserialize)]
248#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
249pub(crate) struct InitSecret {
250 secret: Secret,
251}
252
253impl From<Secret> for InitSecret {
254 fn from(secret: Secret) -> Self {
255 Self { secret }
256 }
257}
258
259fn hpke_info_from_version(version: ProtocolVersion) -> &'static str {
263 match version {
264 ProtocolVersion::Mls10 => "MLS 1.0 external init secret",
265 _ => "<OpenMLS reserved; Don't use this.>",
266 }
267}
268
269impl InitSecret {
270 fn new(
272 crypto: &impl OpenMlsCrypto,
273 ciphersuite: Ciphersuite,
274 epoch_secret: EpochSecret,
275 ) -> Result<Self, CryptoError> {
276 let secret = epoch_secret
277 .secret
278 .derive_secret(crypto, ciphersuite, "init")?;
279 log_crypto!(trace, "Init secret: {:x?}", secret);
280 Ok(InitSecret { secret })
281 }
282
283 pub(crate) fn random(
285 ciphersuite: Ciphersuite,
286 rand: &impl OpenMlsRand,
287 ) -> Result<Self, CryptoError> {
288 Ok(InitSecret {
289 secret: Secret::random(ciphersuite, rand)?,
290 })
291 }
292
293 pub(crate) fn from_group_context(
295 crypto: &impl OpenMlsCrypto,
296 group_context: &GroupContext,
297 external_pub: &[u8],
298 ) -> Result<(Self, Vec<u8>), KeyScheduleError> {
299 let ciphersuite = group_context.ciphersuite();
300 let version = group_context.protocol_version();
301 let (kem_output, raw_init_secret) = crypto.hpke_setup_sender_and_export(
302 ciphersuite.hpke_config(),
303 external_pub,
304 &[],
305 hpke_info_from_version(version).as_bytes(),
306 ciphersuite.hash_length(),
307 )?;
308 Ok((
309 InitSecret {
310 secret: Secret::from_slice(&raw_init_secret),
311 },
312 kem_output,
313 ))
314 }
315
316 pub(crate) fn from_kem_output(
318 crypto: &impl OpenMlsCrypto,
319 ciphersuite: Ciphersuite,
320 version: ProtocolVersion,
321 external_priv: &HpkePrivateKey,
322 kem_output: &[u8],
323 ) -> Result<Self, LibraryError> {
324 let raw_init_secret = crypto
325 .hpke_setup_receiver_and_export(
326 ciphersuite.hpke_config(),
327 kem_output,
328 external_priv,
329 &[],
330 hpke_info_from_version(version).as_bytes(),
331 ciphersuite.hash_length(),
332 )
333 .map_err(LibraryError::unexpected_crypto_error)?;
334 Ok(InitSecret {
335 secret: Secret::from_slice(&raw_init_secret),
336 })
337 }
338
339 #[cfg(any(feature = "test-utils", test))]
340 pub(crate) fn clone(&self) -> Self {
341 Self {
342 secret: self.secret.clone(),
343 }
344 }
345
346 #[cfg(any(feature = "test-utils", test))]
347 pub(crate) fn as_slice(&self) -> &[u8] {
348 self.secret.as_slice()
349 }
350}
351
352#[derive(Debug, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize)]
353pub(crate) struct JoinerSecret {
354 secret: Secret,
355}
356
357impl JoinerSecret {
358 pub(crate) fn new(
363 crypto: &impl OpenMlsCrypto,
364 ciphersuite: Ciphersuite,
365 commit_secret_option: impl Into<Option<CommitSecret>>,
366 init_secret: &InitSecret,
367 serialized_group_context: &[u8],
368 ) -> Result<Self, CryptoError> {
369 let intermediate_secret = init_secret.secret.hkdf_extract(
370 crypto,
371 ciphersuite,
372 commit_secret_option.into().as_ref().map(|cs| &cs.secret),
373 )?;
374 let secret = intermediate_secret.kdf_expand_label(
375 crypto,
376 ciphersuite,
377 "joiner",
378 serialized_group_context,
379 ciphersuite.hash_length(),
380 )?;
381 log_crypto!(trace, "Joiner secret: {:x?}", secret);
382 Ok(JoinerSecret { secret })
383 }
384
385 #[cfg(any(feature = "test-utils", test))]
386 pub(crate) fn as_slice(&self) -> &[u8] {
387 self.secret.as_slice()
388 }
389
390 #[cfg(test)]
391 pub(crate) fn random(ciphersuite: Ciphersuite, rand: &impl OpenMlsRand) -> Self {
392 Self {
393 secret: Secret::random(ciphersuite, rand).expect("Not enough randomness."),
394 }
395 }
396}
397
398#[derive(Debug, PartialEq)]
400enum State {
401 Initial,
402 Context,
403 Done,
404}
405
406pub(crate) struct KeySchedule {
407 ciphersuite: Ciphersuite,
408 intermediate_secret: Option<IntermediateSecret>,
409 epoch_secret: Option<EpochSecret>,
410 state: State,
411}
412
413impl KeySchedule {
414 pub(crate) fn init(
416 ciphersuite: Ciphersuite,
417 crypto: &impl OpenMlsCrypto,
418 joiner_secret: &JoinerSecret,
419 psk: PskSecret,
420 ) -> Result<Self, LibraryError> {
421 log::debug!("Initializing the key schedule with {:?} ...", ciphersuite);
422 log_crypto!(
423 trace,
424 " joiner_secret: {:x?}",
425 joiner_secret.secret.as_slice()
426 );
427 let intermediate_secret = IntermediateSecret::new(crypto, ciphersuite, joiner_secret, psk)
428 .map_err(LibraryError::unexpected_crypto_error)?;
429 Ok(Self {
430 ciphersuite,
431 intermediate_secret: Some(intermediate_secret),
432 epoch_secret: None,
433 state: State::Initial,
434 })
435 }
436
437 pub(crate) fn welcome(
440 &self,
441 crypto: &impl OpenMlsCrypto,
442 ciphersuite: Ciphersuite,
443 ) -> Result<WelcomeSecret, KeyScheduleError> {
444 if self.state != State::Initial || self.intermediate_secret.is_none() {
445 log::error!("Trying to derive a welcome secret while not in the initial state.");
446 return Err(KeyScheduleError::InvalidState(ErrorState::Init));
447 }
448
449 let intermediate_secret = self
451 .intermediate_secret
452 .as_ref()
453 .ok_or_else(|| LibraryError::custom("state machine error"))?;
454
455 Ok(WelcomeSecret::new(
456 crypto,
457 ciphersuite,
458 intermediate_secret,
459 )?)
460 }
461
462 pub(crate) fn add_context(
464 &mut self,
465 crypto: &impl OpenMlsCrypto,
466 serialized_group_context: &[u8],
467 ) -> Result<(), KeyScheduleError> {
468 log::trace!(
469 "Adding context to key schedule. {:?}",
470 serialized_group_context
471 );
472 if self.state != State::Initial || self.intermediate_secret.is_none() {
473 log::error!(
474 "Trying to add context to the key schedule while not in the initial state."
475 );
476 return Err(KeyScheduleError::InvalidState(ErrorState::Init));
477 }
478 self.state = State::Context;
479
480 let intermediate_secret = self
482 .intermediate_secret
483 .take()
484 .ok_or_else(|| LibraryError::custom("state machine error"))?;
485
486 log_crypto!(
487 trace,
488 " intermediate_secret: {:x?}",
489 intermediate_secret.secret.as_slice()
490 );
491
492 self.epoch_secret = Some(EpochSecret::new(
493 self.ciphersuite,
494 crypto,
495 intermediate_secret,
496 serialized_group_context,
497 )?);
498 self.intermediate_secret = None;
499 Ok(())
500 }
501
502 pub(crate) fn epoch_secrets(
506 &mut self,
507 crypto: &impl OpenMlsCrypto,
508 ciphersuite: Ciphersuite,
509 ) -> Result<EpochSecrets, KeyScheduleError> {
510 if self.state != State::Context || self.epoch_secret.is_none() {
511 log::error!("Trying to derive the epoch secrets while not in the right state.");
512 return Err(KeyScheduleError::InvalidState(ErrorState::Context));
513 }
514 self.state = State::Done;
515
516 let epoch_secret = match self.epoch_secret.take() {
517 Some(epoch_secret) => epoch_secret,
518 None => return Err(LibraryError::custom("state machine error").into()),
520 };
521
522 Ok(EpochSecrets::new(crypto, ciphersuite, epoch_secret)?)
523 }
524}
525
526struct IntermediateSecret {
529 secret: Secret,
530}
531
532impl IntermediateSecret {
533 fn new(
536 crypto: &impl OpenMlsCrypto,
537 ciphersuite: Ciphersuite,
538 joiner_secret: &JoinerSecret,
539 psk: PskSecret,
540 ) -> Result<Self, CryptoError> {
541 log_crypto!(trace, "PSK input: {:x?}", psk.as_slice());
542 let secret = joiner_secret
543 .secret
544 .hkdf_extract(crypto, ciphersuite, psk.secret())?;
545 log_crypto!(trace, "Intermediate secret: {:x?}", secret);
546 Ok(Self { secret })
547 }
548}
549
550pub(crate) struct WelcomeSecret {
551 secret: Secret,
552}
553
554impl WelcomeSecret {
555 fn new(
557 crypto: &impl OpenMlsCrypto,
558 ciphersuite: Ciphersuite,
559 intermediate_secret: &IntermediateSecret,
560 ) -> Result<Self, CryptoError> {
561 let secret = intermediate_secret
562 .secret
563 .derive_secret(crypto, ciphersuite, "welcome")?;
564 log_crypto!(trace, "Welcome secret: {:x?}", secret);
565 Ok(WelcomeSecret { secret })
566 }
567
568 pub(crate) fn derive_welcome_key_nonce(
571 self,
572 crypto: &impl OpenMlsCrypto,
573 ciphersuite: Ciphersuite,
574 ) -> Result<(AeadKey, AeadNonce), CryptoError> {
575 let welcome_nonce = self.derive_aead_nonce(crypto, ciphersuite)?;
576 let welcome_key = self.derive_aead_key(crypto, ciphersuite)?;
577 Ok((welcome_key, welcome_nonce))
578 }
579
580 fn derive_aead_key(
582 &self,
583 crypto: &impl OpenMlsCrypto,
584 ciphersuite: Ciphersuite,
585 ) -> Result<AeadKey, CryptoError> {
586 log::trace!("WelcomeSecret.derive_aead_key with {}", ciphersuite);
587 let aead_secret = self.secret.kdf_expand_label(
588 crypto,
589 ciphersuite,
590 "key",
591 b"",
592 ciphersuite.aead_key_length(),
593 )?;
594 Ok(AeadKey::from_secret(aead_secret, ciphersuite))
595 }
596
597 fn derive_aead_nonce(
599 &self,
600 crypto: &impl OpenMlsCrypto,
601 ciphersuite: Ciphersuite,
602 ) -> Result<AeadNonce, CryptoError> {
603 let nonce_secret = self.secret.kdf_expand_label(
604 crypto,
605 ciphersuite,
606 "nonce",
607 b"",
608 ciphersuite.aead_nonce_length(),
609 )?;
610 Ok(AeadNonce::from_secret(nonce_secret))
611 }
612
613 #[cfg(any(feature = "test-utils", test))]
614 pub(crate) fn as_slice(&self) -> &[u8] {
615 self.secret.as_slice()
616 }
617}
618
619struct EpochSecret {
623 secret: Secret,
624}
625
626impl EpochSecret {
627 fn new(
629 ciphersuite: Ciphersuite,
630 crypto: &impl OpenMlsCrypto,
631 intermediate_secret: IntermediateSecret,
632 serialized_group_context: &[u8],
633 ) -> Result<Self, CryptoError> {
634 let secret = intermediate_secret.secret.kdf_expand_label(
635 crypto,
636 ciphersuite,
637 "epoch",
638 serialized_group_context,
639 ciphersuite.hash_length(),
640 )?;
641 log_crypto!(trace, "Epoch secret: {:x?}", secret);
642 Ok(EpochSecret { secret })
643 }
644}
645
646#[cfg_attr(test, derive(Clone))]
648pub(crate) struct EncryptionSecret {
649 secret: Secret,
650}
651
652impl EncryptionSecret {
653 fn new(
655 crypto: &impl OpenMlsCrypto,
656 ciphersuite: Ciphersuite,
657 epoch_secret: &EpochSecret,
658 ) -> Result<Self, CryptoError> {
659 Ok(EncryptionSecret {
660 secret: epoch_secret
661 .secret
662 .derive_secret(crypto, ciphersuite, "encryption")?,
663 })
664 }
665
666 pub(crate) fn create_secret_tree(
669 self,
670 treesize: TreeSize,
671 own_index: LeafNodeIndex,
672 ) -> SecretTree {
673 SecretTree::new(self, treesize, own_index)
674 }
675
676 pub(crate) fn consume_secret(self) -> Secret {
677 self.secret
678 }
679
680 #[cfg(test)]
682 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
683 EncryptionSecret {
684 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
685 }
686 }
687
688 #[cfg(any(feature = "test-utils", test))]
689 pub(crate) fn as_slice(&self) -> &[u8] {
690 self.secret.as_slice()
691 }
692
693 #[cfg(any(feature = "test-utils", test))]
694 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
696 Self {
697 secret: Secret::from_slice(bytes),
698 }
699 }
700}
701
702#[derive(Debug, Serialize, Deserialize)]
704#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
705pub(crate) struct ExporterSecret {
706 secret: Secret,
707}
708
709impl ExporterSecret {
710 fn new(
712 crypto: &impl OpenMlsCrypto,
713 ciphersuite: Ciphersuite,
714 epoch_secret: &EpochSecret,
715 ) -> Result<Self, CryptoError> {
716 let secret = epoch_secret
717 .secret
718 .derive_secret(crypto, ciphersuite, "exporter")?;
719 Ok(ExporterSecret { secret })
720 }
721
722 #[cfg(any(feature = "test-utils", test))]
723 pub(crate) fn as_slice(&self) -> &[u8] {
724 self.secret.as_slice()
725 }
726
727 pub(crate) fn derive_exported_secret(
731 &self,
732 ciphersuite: Ciphersuite,
733 crypto: &impl OpenMlsCrypto,
734 label: &str,
735 context: &[u8],
736 key_length: usize,
737 ) -> Result<Vec<u8>, CryptoError> {
738 let context_hash = &crypto.hash(ciphersuite.hash_algorithm(), context)?;
739 Ok(self
740 .secret
741 .derive_secret(crypto, ciphersuite, label)?
742 .kdf_expand_label(crypto, ciphersuite, "exported", context_hash, key_length)?
743 .as_slice()
744 .to_vec())
745 }
746}
747
748#[derive(Debug, Serialize, Deserialize)]
750#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
751pub(crate) struct ExternalSecret {
752 secret: Secret,
753}
754
755impl ExternalSecret {
756 fn new(
758 crypto: &impl OpenMlsCrypto,
759 ciphersuite: Ciphersuite,
760 epoch_secret: &EpochSecret,
761 ) -> Result<Self, CryptoError> {
762 let secret = epoch_secret
763 .secret
764 .derive_secret(crypto, ciphersuite, "external")?;
765 Ok(Self { secret })
766 }
767
768 pub(crate) fn derive_external_keypair(
770 &self,
771 crypto: &impl OpenMlsCrypto,
772 ciphersuite: Ciphersuite,
773 ) -> Result<HpkeKeyPair, CryptoError> {
774 crypto.derive_hpke_keypair(ciphersuite.hpke_config(), self.secret.as_slice())
775 }
776
777 #[cfg(any(feature = "test-utils", test))]
778 pub(crate) fn as_slice(&self) -> &[u8] {
779 self.secret.as_slice()
780 }
781}
782
783#[derive(Debug, Serialize, Deserialize)]
785#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
786pub(crate) struct ConfirmationKey {
787 secret: Secret,
788}
789
790impl ConfirmationKey {
791 fn new(
793 crypto: &impl OpenMlsCrypto,
794 ciphersuite: Ciphersuite,
795 epoch_secret: &EpochSecret,
796 ) -> Result<Self, CryptoError> {
797 log::debug!("Computing confirmation key.");
798 log_crypto!(
799 trace,
800 " epoch_secret {:x?}",
801 epoch_secret.secret.as_slice()
802 );
803 let secret = epoch_secret
804 .secret
805 .derive_secret(crypto, ciphersuite, "confirm")?;
806 Ok(Self { secret })
807 }
808
809 pub(crate) fn tag(
818 &self,
819 crypto: &impl OpenMlsCrypto,
820 ciphersuite: Ciphersuite,
821 confirmed_transcript_hash: &[u8],
822 ) -> Result<ConfirmationTag, CryptoError> {
823 log::debug!("Computing confirmation tag.");
824 log_crypto!(trace, " confirmation key {:x?}", self.secret.as_slice());
825 log_crypto!(trace, " transcript hash {:x?}", confirmed_transcript_hash);
826 Ok(ConfirmationTag(Mac::new(
827 crypto,
828 ciphersuite,
829 &self.secret,
830 confirmed_transcript_hash,
831 )?))
832 }
833}
834
835#[cfg(test)]
836impl ConfirmationKey {
837 pub(crate) fn from_secret(secret: Secret) -> Self {
838 Self { secret }
839 }
840}
841
842#[cfg(any(feature = "test-utils", test))]
843impl ConfirmationKey {
844 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
845 Self {
846 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
847 }
848 }
849
850 pub(crate) fn as_slice(&self) -> &[u8] {
851 self.secret.as_slice()
852 }
853}
854
855#[derive(Debug, Serialize, Deserialize)]
857#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
858pub(crate) struct MembershipKey {
859 secret: Secret,
860}
861
862impl MembershipKey {
863 fn new(
865 crypto: &impl OpenMlsCrypto,
866 ciphersuite: Ciphersuite,
867 epoch_secret: &EpochSecret,
868 ) -> Result<Self, CryptoError> {
869 let secret = epoch_secret
870 .secret
871 .derive_secret(crypto, ciphersuite, "membership")?;
872 Ok(Self { secret })
873 }
874
875 pub(crate) fn tag_message(
883 &self,
884 crypto: &impl OpenMlsCrypto,
885 ciphersuite: Ciphersuite,
886 tbm_payload: AuthenticatedContentTbm,
887 ) -> Result<MembershipTag, LibraryError> {
888 Ok(MembershipTag(
889 Mac::new(
890 crypto,
891 ciphersuite,
892 &self.secret,
893 &tbm_payload
894 .into_bytes()
895 .map_err(LibraryError::missing_bound_check)?,
896 )
897 .map_err(LibraryError::unexpected_crypto_error)?,
898 ))
899 }
900
901 #[cfg(any(feature = "test-utils", test))]
902 pub(crate) fn from_secret(secret: Secret) -> Self {
903 Self { secret }
904 }
905
906 #[cfg(any(feature = "test-utils", test))]
907 pub(crate) fn as_slice(&self) -> &[u8] {
908 self.secret.as_slice()
909 }
910
911 #[cfg(any(feature = "test-utils", test))]
912 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
913 Self {
914 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
915 }
916 }
917}
918
919fn ciphertext_sample(ciphersuite: Ciphersuite, ciphertext: &[u8]) -> &[u8] {
921 let sample_length = ciphersuite.hash_length();
922 log::debug!("Getting ciphertext sample of length {:?}", sample_length);
923 if ciphertext.len() <= sample_length {
924 ciphertext
925 } else {
926 &ciphertext[0..sample_length]
927 }
928}
929
930#[derive(Serialize, Deserialize)]
932#[cfg_attr(
933 any(feature = "test-utils", feature = "crypto-debug", test),
934 derive(Debug, Clone, PartialEq)
935)]
936pub(crate) struct SenderDataSecret {
937 secret: Secret,
938}
939
940impl SenderDataSecret {
941 fn new(
943 crypto: &impl OpenMlsCrypto,
944 ciphersuite: Ciphersuite,
945 epoch_secret: &EpochSecret,
946 ) -> Result<Self, CryptoError> {
947 let secret = epoch_secret
948 .secret
949 .derive_secret(crypto, ciphersuite, "sender data")?;
950 Ok(SenderDataSecret { secret })
951 }
952
953 pub(crate) fn derive_aead_key(
955 &self,
956 crypto: &impl OpenMlsCrypto,
957 ciphersuite: Ciphersuite,
958 ciphertext: &[u8],
959 ) -> Result<AeadKey, CryptoError> {
960 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
961 log::debug!(
962 "SenderDataSecret::derive_aead_key ciphertext sample: {:x?}",
963 ciphertext_sample
964 );
965 let secret = self.secret.kdf_expand_label(
966 crypto,
967 ciphersuite,
968 "key",
969 ciphertext_sample,
970 ciphersuite.aead_key_length(),
971 )?;
972 Ok(AeadKey::from_secret(secret, ciphersuite))
973 }
974
975 pub(crate) fn derive_aead_nonce(
977 &self,
978 ciphersuite: Ciphersuite,
979 crypto: &impl OpenMlsCrypto,
980 ciphertext: &[u8],
981 ) -> Result<AeadNonce, CryptoError> {
982 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
983 log::debug!(
984 "SenderDataSecret::derive_aead_nonce ciphertext sample: {:x?}",
985 ciphertext_sample
986 );
987 let nonce_secret = self.secret.kdf_expand_label(
988 crypto,
989 ciphersuite,
990 "nonce",
991 ciphertext_sample,
992 ciphersuite.aead_nonce_length(),
993 )?;
994 Ok(AeadNonce::from_secret(nonce_secret))
995 }
996
997 #[cfg(any(feature = "test-utils", test))]
998 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
999 Self {
1000 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
1001 }
1002 }
1003
1004 #[cfg(any(feature = "test-utils", test))]
1005 pub(crate) fn as_slice(&self) -> &[u8] {
1006 self.secret.as_slice()
1007 }
1008
1009 #[cfg(any(feature = "test-utils", test))]
1010 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1012 Self {
1013 secret: Secret::from_slice(bytes),
1014 }
1015 }
1016}
1017
1018pub(crate) struct EpochSecrets {
1034 init_secret: InitSecret,
1035 sender_data_secret: SenderDataSecret,
1036 encryption_secret: EncryptionSecret,
1037 exporter_secret: ExporterSecret,
1038 epoch_authenticator: EpochAuthenticator,
1039 external_secret: ExternalSecret,
1040 confirmation_key: ConfirmationKey,
1041 membership_key: MembershipKey,
1042 resumption_psk: ResumptionPskSecret,
1043}
1044
1045impl std::fmt::Debug for EpochSecrets {
1046 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1047 f.write_str("EpochSecrets { *** }")
1048 }
1049}
1050
1051#[cfg(not(test))]
1052impl PartialEq for EpochSecrets {
1053 fn eq(&self, _other: &Self) -> bool {
1054 false
1055 }
1056}
1057
1058#[cfg(test)]
1060impl PartialEq for EpochSecrets {
1061 fn eq(&self, other: &Self) -> bool {
1062 self.sender_data_secret == other.sender_data_secret
1063 && self.exporter_secret == other.exporter_secret
1064 && self.epoch_authenticator == other.epoch_authenticator
1065 && self.external_secret == other.external_secret
1066 && self.confirmation_key == other.confirmation_key
1067 && self.membership_key == other.membership_key
1068 && self.resumption_psk == other.resumption_psk
1069 }
1070}
1071
1072impl EpochSecrets {
1073 #[cfg(any(feature = "test-utils", test))]
1075 pub(crate) fn sender_data_secret(&self) -> &SenderDataSecret {
1076 &self.sender_data_secret
1077 }
1078
1079 pub(crate) fn confirmation_key(&self) -> &ConfirmationKey {
1081 &self.confirmation_key
1082 }
1083
1084 #[cfg(any(feature = "test-utils", test))]
1086 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1087 &self.epoch_authenticator
1088 }
1089
1090 #[cfg(any(feature = "test-utils", test))]
1092 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1093 &self.exporter_secret
1094 }
1095
1096 #[cfg(any(feature = "test-utils", test))]
1098 pub(crate) fn membership_key(&self) -> &MembershipKey {
1099 &self.membership_key
1100 }
1101
1102 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1104 &self.external_secret
1105 }
1106
1107 #[cfg(any(feature = "test-utils", test))]
1109 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1110 &self.resumption_psk
1111 }
1112
1113 #[cfg(any(feature = "test-utils", test))]
1115 pub(crate) fn init_secret(&self) -> &InitSecret {
1116 &self.init_secret
1117 }
1118
1119 #[cfg(any(feature = "test-utils", test))]
1121 pub(crate) fn encryption_secret(&self) -> &EncryptionSecret {
1122 &self.encryption_secret
1123 }
1124
1125 fn new(
1129 crypto: &impl OpenMlsCrypto,
1130 ciphersuite: Ciphersuite,
1131 epoch_secret: EpochSecret,
1132 ) -> Result<Self, CryptoError> {
1133 log::debug!(
1134 "Computing EpochSecrets from epoch secret with {}",
1135 ciphersuite
1136 );
1137 log_crypto!(
1138 trace,
1139 " epoch_secret: {:x?}",
1140 epoch_secret.secret.as_slice()
1141 );
1142 let sender_data_secret = SenderDataSecret::new(crypto, ciphersuite, &epoch_secret)?;
1143 let encryption_secret = EncryptionSecret::new(crypto, ciphersuite, &epoch_secret)?;
1144 let exporter_secret = ExporterSecret::new(crypto, ciphersuite, &epoch_secret)?;
1145 let epoch_authenticator = EpochAuthenticator::new(crypto, ciphersuite, &epoch_secret)?;
1146 let external_secret = ExternalSecret::new(crypto, ciphersuite, &epoch_secret)?;
1147 let confirmation_key = ConfirmationKey::new(crypto, ciphersuite, &epoch_secret)?;
1148 let membership_key = MembershipKey::new(crypto, ciphersuite, &epoch_secret)?;
1149 let resumption_psk = ResumptionPskSecret::new(crypto, ciphersuite, &epoch_secret)?;
1150
1151 log::trace!(" Computing init secret.");
1152 let init_secret = InitSecret::new(crypto, ciphersuite, epoch_secret)?;
1153
1154 Ok(EpochSecrets {
1155 init_secret,
1156 sender_data_secret,
1157 encryption_secret,
1158 exporter_secret,
1159 epoch_authenticator,
1160 external_secret,
1161 confirmation_key,
1162 membership_key,
1163 resumption_psk,
1164 })
1165 }
1166
1167 pub(crate) fn with_init_secret(
1172 crypto: &impl OpenMlsCrypto,
1173 ciphersuite: Ciphersuite,
1174 init_secret: InitSecret,
1175 ) -> Result<Self, CryptoError> {
1176 let epoch_secret = EpochSecret {
1177 secret: Secret::zero(ciphersuite),
1178 };
1179 let mut epoch_secrets = Self::new(crypto, ciphersuite, epoch_secret)?;
1180 epoch_secrets.init_secret = init_secret;
1181 Ok(epoch_secrets)
1182 }
1183
1184 pub(crate) fn split_secrets(
1189 self,
1190 serialized_context: Vec<u8>,
1191 treesize: TreeSize,
1192 own_index: LeafNodeIndex,
1193 ) -> (GroupEpochSecrets, MessageSecrets) {
1194 let secret_tree = self
1195 .encryption_secret
1196 .create_secret_tree(treesize, own_index);
1197 (
1198 GroupEpochSecrets {
1199 init_secret: self.init_secret,
1200 exporter_secret: self.exporter_secret,
1201 epoch_authenticator: self.epoch_authenticator,
1202 external_secret: self.external_secret,
1203 resumption_psk: self.resumption_psk,
1204 },
1205 MessageSecrets::new(
1206 self.sender_data_secret,
1207 self.membership_key,
1208 self.confirmation_key,
1209 serialized_context,
1210 secret_tree,
1211 ),
1212 )
1213 }
1214}
1215
1216#[derive(Serialize, Deserialize)]
1217#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
1218pub(crate) struct GroupEpochSecrets {
1219 init_secret: InitSecret,
1220 exporter_secret: ExporterSecret,
1221 epoch_authenticator: EpochAuthenticator,
1222 external_secret: ExternalSecret,
1223 resumption_psk: ResumptionPskSecret,
1224}
1225
1226impl std::fmt::Debug for GroupEpochSecrets {
1227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1228 f.write_str("GroupEpochSecrets { *** }")
1229 }
1230}
1231
1232#[cfg(not(any(test, feature = "test-utils")))]
1233impl PartialEq for GroupEpochSecrets {
1234 fn eq(&self, _other: &Self) -> bool {
1235 false
1236 }
1237}
1238
1239impl GroupEpochSecrets {
1240 pub(crate) fn init_secret(&self) -> &InitSecret {
1242 &self.init_secret
1243 }
1244
1245 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1247 &self.epoch_authenticator
1248 }
1249
1250 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1252 &self.exporter_secret
1253 }
1254
1255 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1257 &self.external_secret
1258 }
1259
1260 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1262 &self.resumption_psk
1263 }
1264}