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!("Adding context to key schedule. {serialized_group_context:?}");
469 if self.state != State::Initial || self.intermediate_secret.is_none() {
470 log::error!(
471 "Trying to add context to the key schedule while not in the initial state."
472 );
473 return Err(KeyScheduleError::InvalidState(ErrorState::Init));
474 }
475 self.state = State::Context;
476
477 let intermediate_secret = self
479 .intermediate_secret
480 .take()
481 .ok_or_else(|| LibraryError::custom("state machine error"))?;
482
483 log_crypto!(
484 trace,
485 " intermediate_secret: {:x?}",
486 intermediate_secret.secret.as_slice()
487 );
488
489 self.epoch_secret = Some(EpochSecret::new(
490 self.ciphersuite,
491 crypto,
492 intermediate_secret,
493 serialized_group_context,
494 )?);
495 self.intermediate_secret = None;
496 Ok(())
497 }
498
499 pub(crate) fn epoch_secrets(
503 &mut self,
504 crypto: &impl OpenMlsCrypto,
505 ciphersuite: Ciphersuite,
506 ) -> Result<EpochSecrets, KeyScheduleError> {
507 if self.state != State::Context || self.epoch_secret.is_none() {
508 log::error!("Trying to derive the epoch secrets while not in the right state.");
509 return Err(KeyScheduleError::InvalidState(ErrorState::Context));
510 }
511 self.state = State::Done;
512
513 let epoch_secret = match self.epoch_secret.take() {
514 Some(epoch_secret) => epoch_secret,
515 None => return Err(LibraryError::custom("state machine error").into()),
517 };
518
519 Ok(EpochSecrets::new(crypto, ciphersuite, epoch_secret)?)
520 }
521}
522
523struct IntermediateSecret {
526 secret: Secret,
527}
528
529impl IntermediateSecret {
530 fn new(
533 crypto: &impl OpenMlsCrypto,
534 ciphersuite: Ciphersuite,
535 joiner_secret: &JoinerSecret,
536 psk: PskSecret,
537 ) -> Result<Self, CryptoError> {
538 log_crypto!(trace, "PSK input: {:x?}", psk.as_slice());
539 let secret = joiner_secret
540 .secret
541 .hkdf_extract(crypto, ciphersuite, psk.secret())?;
542 log_crypto!(trace, "Intermediate secret: {:x?}", secret);
543 Ok(Self { secret })
544 }
545}
546
547pub(crate) struct WelcomeSecret {
548 secret: Secret,
549}
550
551impl WelcomeSecret {
552 fn new(
554 crypto: &impl OpenMlsCrypto,
555 ciphersuite: Ciphersuite,
556 intermediate_secret: &IntermediateSecret,
557 ) -> Result<Self, CryptoError> {
558 let secret = intermediate_secret
559 .secret
560 .derive_secret(crypto, ciphersuite, "welcome")?;
561 log_crypto!(trace, "Welcome secret: {:x?}", secret);
562 Ok(WelcomeSecret { secret })
563 }
564
565 pub(crate) fn derive_welcome_key_nonce(
568 self,
569 crypto: &impl OpenMlsCrypto,
570 ciphersuite: Ciphersuite,
571 ) -> Result<(AeadKey, AeadNonce), CryptoError> {
572 let welcome_nonce = self.derive_aead_nonce(crypto, ciphersuite)?;
573 let welcome_key = self.derive_aead_key(crypto, ciphersuite)?;
574 Ok((welcome_key, welcome_nonce))
575 }
576
577 fn derive_aead_key(
579 &self,
580 crypto: &impl OpenMlsCrypto,
581 ciphersuite: Ciphersuite,
582 ) -> Result<AeadKey, CryptoError> {
583 log::trace!("WelcomeSecret.derive_aead_key with {ciphersuite}");
584 let aead_secret = self.secret.kdf_expand_label(
585 crypto,
586 ciphersuite,
587 "key",
588 b"",
589 ciphersuite.aead_key_length(),
590 )?;
591 Ok(AeadKey::from_secret(aead_secret, ciphersuite))
592 }
593
594 fn derive_aead_nonce(
596 &self,
597 crypto: &impl OpenMlsCrypto,
598 ciphersuite: Ciphersuite,
599 ) -> Result<AeadNonce, CryptoError> {
600 let nonce_secret = self.secret.kdf_expand_label(
601 crypto,
602 ciphersuite,
603 "nonce",
604 b"",
605 ciphersuite.aead_nonce_length(),
606 )?;
607 Ok(AeadNonce::from_secret(nonce_secret))
608 }
609
610 #[cfg(any(feature = "test-utils", test))]
611 pub(crate) fn as_slice(&self) -> &[u8] {
612 self.secret.as_slice()
613 }
614}
615
616struct EpochSecret {
620 secret: Secret,
621}
622
623impl EpochSecret {
624 fn new(
626 ciphersuite: Ciphersuite,
627 crypto: &impl OpenMlsCrypto,
628 intermediate_secret: IntermediateSecret,
629 serialized_group_context: &[u8],
630 ) -> Result<Self, CryptoError> {
631 let secret = intermediate_secret.secret.kdf_expand_label(
632 crypto,
633 ciphersuite,
634 "epoch",
635 serialized_group_context,
636 ciphersuite.hash_length(),
637 )?;
638 log_crypto!(trace, "Epoch secret: {:x?}", secret);
639 Ok(EpochSecret { secret })
640 }
641}
642
643#[cfg_attr(test, derive(Clone))]
645pub(crate) struct EncryptionSecret {
646 secret: Secret,
647}
648
649impl EncryptionSecret {
650 fn new(
652 crypto: &impl OpenMlsCrypto,
653 ciphersuite: Ciphersuite,
654 epoch_secret: &EpochSecret,
655 ) -> Result<Self, CryptoError> {
656 Ok(EncryptionSecret {
657 secret: epoch_secret
658 .secret
659 .derive_secret(crypto, ciphersuite, "encryption")?,
660 })
661 }
662
663 pub(crate) fn create_secret_tree(
666 self,
667 treesize: TreeSize,
668 own_index: LeafNodeIndex,
669 ) -> SecretTree {
670 SecretTree::new(self, treesize, own_index)
671 }
672
673 pub(crate) fn consume_secret(self) -> Secret {
674 self.secret
675 }
676
677 #[cfg(test)]
679 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
680 EncryptionSecret {
681 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
682 }
683 }
684
685 #[cfg(any(feature = "test-utils", test))]
686 pub(crate) fn as_slice(&self) -> &[u8] {
687 self.secret.as_slice()
688 }
689
690 #[cfg(any(feature = "test-utils", test))]
691 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
693 Self {
694 secret: Secret::from_slice(bytes),
695 }
696 }
697}
698
699#[derive(Debug, Serialize, Deserialize)]
701#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
702pub(crate) struct ExporterSecret {
703 secret: Secret,
704}
705
706impl ExporterSecret {
707 fn new(
709 crypto: &impl OpenMlsCrypto,
710 ciphersuite: Ciphersuite,
711 epoch_secret: &EpochSecret,
712 ) -> Result<Self, CryptoError> {
713 let secret = epoch_secret
714 .secret
715 .derive_secret(crypto, ciphersuite, "exporter")?;
716 Ok(ExporterSecret { secret })
717 }
718
719 #[cfg(any(feature = "test-utils", test))]
720 pub(crate) fn as_slice(&self) -> &[u8] {
721 self.secret.as_slice()
722 }
723
724 pub(crate) fn derive_exported_secret(
728 &self,
729 ciphersuite: Ciphersuite,
730 crypto: &impl OpenMlsCrypto,
731 label: &str,
732 context: &[u8],
733 key_length: usize,
734 ) -> Result<Vec<u8>, CryptoError> {
735 let context_hash = &crypto.hash(ciphersuite.hash_algorithm(), context)?;
736 Ok(self
737 .secret
738 .derive_secret(crypto, ciphersuite, label)?
739 .kdf_expand_label(crypto, ciphersuite, "exported", context_hash, key_length)?
740 .as_slice()
741 .to_vec())
742 }
743}
744
745#[derive(Debug, Serialize, Deserialize)]
747#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
748pub(crate) struct ExternalSecret {
749 secret: Secret,
750}
751
752impl ExternalSecret {
753 fn new(
755 crypto: &impl OpenMlsCrypto,
756 ciphersuite: Ciphersuite,
757 epoch_secret: &EpochSecret,
758 ) -> Result<Self, CryptoError> {
759 let secret = epoch_secret
760 .secret
761 .derive_secret(crypto, ciphersuite, "external")?;
762 Ok(Self { secret })
763 }
764
765 pub(crate) fn derive_external_keypair(
767 &self,
768 crypto: &impl OpenMlsCrypto,
769 ciphersuite: Ciphersuite,
770 ) -> Result<HpkeKeyPair, CryptoError> {
771 crypto.derive_hpke_keypair(ciphersuite.hpke_config(), self.secret.as_slice())
772 }
773
774 #[cfg(any(feature = "test-utils", test))]
775 pub(crate) fn as_slice(&self) -> &[u8] {
776 self.secret.as_slice()
777 }
778}
779
780#[derive(Debug, Serialize, Deserialize)]
782#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
783pub(crate) struct ConfirmationKey {
784 secret: Secret,
785}
786
787impl ConfirmationKey {
788 fn new(
790 crypto: &impl OpenMlsCrypto,
791 ciphersuite: Ciphersuite,
792 epoch_secret: &EpochSecret,
793 ) -> Result<Self, CryptoError> {
794 log::debug!("Computing confirmation key.");
795 log_crypto!(
796 trace,
797 " epoch_secret {:x?}",
798 epoch_secret.secret.as_slice()
799 );
800 let secret = epoch_secret
801 .secret
802 .derive_secret(crypto, ciphersuite, "confirm")?;
803 Ok(Self { secret })
804 }
805
806 pub(crate) fn tag(
815 &self,
816 crypto: &impl OpenMlsCrypto,
817 ciphersuite: Ciphersuite,
818 confirmed_transcript_hash: &[u8],
819 ) -> Result<ConfirmationTag, CryptoError> {
820 log::debug!("Computing confirmation tag.");
821 log_crypto!(trace, " confirmation key {:x?}", self.secret.as_slice());
822 log_crypto!(trace, " transcript hash {:x?}", confirmed_transcript_hash);
823 Ok(ConfirmationTag(Mac::new(
824 crypto,
825 ciphersuite,
826 &self.secret,
827 confirmed_transcript_hash,
828 )?))
829 }
830}
831
832#[cfg(test)]
833impl ConfirmationKey {
834 pub(crate) fn from_secret(secret: Secret) -> Self {
835 Self { secret }
836 }
837}
838
839#[cfg(any(feature = "test-utils", test))]
840impl ConfirmationKey {
841 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
842 Self {
843 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
844 }
845 }
846
847 pub(crate) fn as_slice(&self) -> &[u8] {
848 self.secret.as_slice()
849 }
850}
851
852#[derive(Debug, Serialize, Deserialize)]
854#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
855pub(crate) struct MembershipKey {
856 secret: Secret,
857}
858
859impl MembershipKey {
860 fn new(
862 crypto: &impl OpenMlsCrypto,
863 ciphersuite: Ciphersuite,
864 epoch_secret: &EpochSecret,
865 ) -> Result<Self, CryptoError> {
866 let secret = epoch_secret
867 .secret
868 .derive_secret(crypto, ciphersuite, "membership")?;
869 Ok(Self { secret })
870 }
871
872 pub(crate) fn tag_message(
880 &self,
881 crypto: &impl OpenMlsCrypto,
882 ciphersuite: Ciphersuite,
883 tbm_payload: AuthenticatedContentTbm,
884 ) -> Result<MembershipTag, LibraryError> {
885 Ok(MembershipTag(
886 Mac::new(
887 crypto,
888 ciphersuite,
889 &self.secret,
890 &tbm_payload
891 .into_bytes()
892 .map_err(LibraryError::missing_bound_check)?,
893 )
894 .map_err(LibraryError::unexpected_crypto_error)?,
895 ))
896 }
897
898 #[cfg(any(feature = "test-utils", test))]
899 pub(crate) fn from_secret(secret: Secret) -> Self {
900 Self { secret }
901 }
902
903 #[cfg(any(feature = "test-utils", test))]
904 pub(crate) fn as_slice(&self) -> &[u8] {
905 self.secret.as_slice()
906 }
907
908 #[cfg(any(feature = "test-utils", test))]
909 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
910 Self {
911 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
912 }
913 }
914}
915
916fn ciphertext_sample(ciphersuite: Ciphersuite, ciphertext: &[u8]) -> &[u8] {
918 let sample_length = ciphersuite.hash_length();
919 log::debug!("Getting ciphertext sample of length {sample_length:?}");
920 if ciphertext.len() <= sample_length {
921 ciphertext
922 } else {
923 &ciphertext[0..sample_length]
924 }
925}
926
927#[derive(Serialize, Deserialize)]
929#[cfg_attr(
930 any(feature = "test-utils", feature = "crypto-debug", test),
931 derive(Debug, Clone, PartialEq)
932)]
933pub(crate) struct SenderDataSecret {
934 secret: Secret,
935}
936
937impl SenderDataSecret {
938 fn new(
940 crypto: &impl OpenMlsCrypto,
941 ciphersuite: Ciphersuite,
942 epoch_secret: &EpochSecret,
943 ) -> Result<Self, CryptoError> {
944 let secret = epoch_secret
945 .secret
946 .derive_secret(crypto, ciphersuite, "sender data")?;
947 Ok(SenderDataSecret { secret })
948 }
949
950 pub(crate) fn derive_aead_key(
952 &self,
953 crypto: &impl OpenMlsCrypto,
954 ciphersuite: Ciphersuite,
955 ciphertext: &[u8],
956 ) -> Result<AeadKey, CryptoError> {
957 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
958 log::debug!("SenderDataSecret::derive_aead_key ciphertext sample: {ciphertext_sample:x?}");
959 let secret = self.secret.kdf_expand_label(
960 crypto,
961 ciphersuite,
962 "key",
963 ciphertext_sample,
964 ciphersuite.aead_key_length(),
965 )?;
966 Ok(AeadKey::from_secret(secret, ciphersuite))
967 }
968
969 pub(crate) fn derive_aead_nonce(
971 &self,
972 ciphersuite: Ciphersuite,
973 crypto: &impl OpenMlsCrypto,
974 ciphertext: &[u8],
975 ) -> Result<AeadNonce, CryptoError> {
976 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
977 log::debug!(
978 "SenderDataSecret::derive_aead_nonce ciphertext sample: {ciphertext_sample:x?}"
979 );
980 let nonce_secret = self.secret.kdf_expand_label(
981 crypto,
982 ciphersuite,
983 "nonce",
984 ciphertext_sample,
985 ciphersuite.aead_nonce_length(),
986 )?;
987 Ok(AeadNonce::from_secret(nonce_secret))
988 }
989
990 #[cfg(any(feature = "test-utils", test))]
991 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
992 Self {
993 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
994 }
995 }
996
997 #[cfg(any(feature = "test-utils", test))]
998 pub(crate) fn as_slice(&self) -> &[u8] {
999 self.secret.as_slice()
1000 }
1001
1002 #[cfg(any(feature = "test-utils", test))]
1003 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1005 Self {
1006 secret: Secret::from_slice(bytes),
1007 }
1008 }
1009}
1010
1011pub(crate) struct EpochSecrets {
1027 init_secret: InitSecret,
1028 sender_data_secret: SenderDataSecret,
1029 encryption_secret: EncryptionSecret,
1030 exporter_secret: ExporterSecret,
1031 epoch_authenticator: EpochAuthenticator,
1032 external_secret: ExternalSecret,
1033 confirmation_key: ConfirmationKey,
1034 membership_key: MembershipKey,
1035 resumption_psk: ResumptionPskSecret,
1036}
1037
1038impl std::fmt::Debug for EpochSecrets {
1039 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1040 f.write_str("EpochSecrets { *** }")
1041 }
1042}
1043
1044#[cfg(not(test))]
1045impl PartialEq for EpochSecrets {
1046 fn eq(&self, _other: &Self) -> bool {
1047 false
1048 }
1049}
1050
1051#[cfg(test)]
1053impl PartialEq for EpochSecrets {
1054 fn eq(&self, other: &Self) -> bool {
1055 self.sender_data_secret == other.sender_data_secret
1056 && self.exporter_secret == other.exporter_secret
1057 && self.epoch_authenticator == other.epoch_authenticator
1058 && self.external_secret == other.external_secret
1059 && self.confirmation_key == other.confirmation_key
1060 && self.membership_key == other.membership_key
1061 && self.resumption_psk == other.resumption_psk
1062 }
1063}
1064
1065impl EpochSecrets {
1066 #[cfg(any(feature = "test-utils", test))]
1068 pub(crate) fn sender_data_secret(&self) -> &SenderDataSecret {
1069 &self.sender_data_secret
1070 }
1071
1072 pub(crate) fn confirmation_key(&self) -> &ConfirmationKey {
1074 &self.confirmation_key
1075 }
1076
1077 #[cfg(any(feature = "test-utils", test))]
1079 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1080 &self.epoch_authenticator
1081 }
1082
1083 #[cfg(any(feature = "test-utils", test))]
1085 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1086 &self.exporter_secret
1087 }
1088
1089 #[cfg(any(feature = "test-utils", test))]
1091 pub(crate) fn membership_key(&self) -> &MembershipKey {
1092 &self.membership_key
1093 }
1094
1095 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1097 &self.external_secret
1098 }
1099
1100 #[cfg(any(feature = "test-utils", test))]
1102 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1103 &self.resumption_psk
1104 }
1105
1106 #[cfg(any(feature = "test-utils", test))]
1108 pub(crate) fn init_secret(&self) -> &InitSecret {
1109 &self.init_secret
1110 }
1111
1112 #[cfg(any(feature = "test-utils", test))]
1114 pub(crate) fn encryption_secret(&self) -> &EncryptionSecret {
1115 &self.encryption_secret
1116 }
1117
1118 fn new(
1122 crypto: &impl OpenMlsCrypto,
1123 ciphersuite: Ciphersuite,
1124 epoch_secret: EpochSecret,
1125 ) -> Result<Self, CryptoError> {
1126 log::debug!("Computing EpochSecrets from epoch secret with {ciphersuite}");
1127 log_crypto!(
1128 trace,
1129 " epoch_secret: {:x?}",
1130 epoch_secret.secret.as_slice()
1131 );
1132 let sender_data_secret = SenderDataSecret::new(crypto, ciphersuite, &epoch_secret)?;
1133 let encryption_secret = EncryptionSecret::new(crypto, ciphersuite, &epoch_secret)?;
1134 let exporter_secret = ExporterSecret::new(crypto, ciphersuite, &epoch_secret)?;
1135 let epoch_authenticator = EpochAuthenticator::new(crypto, ciphersuite, &epoch_secret)?;
1136 let external_secret = ExternalSecret::new(crypto, ciphersuite, &epoch_secret)?;
1137 let confirmation_key = ConfirmationKey::new(crypto, ciphersuite, &epoch_secret)?;
1138 let membership_key = MembershipKey::new(crypto, ciphersuite, &epoch_secret)?;
1139 let resumption_psk = ResumptionPskSecret::new(crypto, ciphersuite, &epoch_secret)?;
1140
1141 log::trace!(" Computing init secret.");
1142 let init_secret = InitSecret::new(crypto, ciphersuite, epoch_secret)?;
1143
1144 Ok(EpochSecrets {
1145 init_secret,
1146 sender_data_secret,
1147 encryption_secret,
1148 exporter_secret,
1149 epoch_authenticator,
1150 external_secret,
1151 confirmation_key,
1152 membership_key,
1153 resumption_psk,
1154 })
1155 }
1156
1157 pub(crate) fn with_init_secret(
1162 crypto: &impl OpenMlsCrypto,
1163 ciphersuite: Ciphersuite,
1164 init_secret: InitSecret,
1165 ) -> Result<Self, CryptoError> {
1166 let epoch_secret = EpochSecret {
1167 secret: Secret::zero(ciphersuite),
1168 };
1169 let mut epoch_secrets = Self::new(crypto, ciphersuite, epoch_secret)?;
1170 epoch_secrets.init_secret = init_secret;
1171 Ok(epoch_secrets)
1172 }
1173
1174 pub(crate) fn split_secrets(
1179 self,
1180 serialized_context: Vec<u8>,
1181 treesize: TreeSize,
1182 own_index: LeafNodeIndex,
1183 ) -> (GroupEpochSecrets, MessageSecrets) {
1184 let secret_tree = self
1185 .encryption_secret
1186 .create_secret_tree(treesize, own_index);
1187 (
1188 GroupEpochSecrets {
1189 init_secret: self.init_secret,
1190 exporter_secret: self.exporter_secret,
1191 epoch_authenticator: self.epoch_authenticator,
1192 external_secret: self.external_secret,
1193 resumption_psk: self.resumption_psk,
1194 },
1195 MessageSecrets::new(
1196 self.sender_data_secret,
1197 self.membership_key,
1198 self.confirmation_key,
1199 serialized_context,
1200 secret_tree,
1201 ),
1202 )
1203 }
1204}
1205
1206#[derive(Serialize, Deserialize)]
1207#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
1208pub(crate) struct GroupEpochSecrets {
1209 init_secret: InitSecret,
1210 exporter_secret: ExporterSecret,
1211 epoch_authenticator: EpochAuthenticator,
1212 external_secret: ExternalSecret,
1213 resumption_psk: ResumptionPskSecret,
1214}
1215
1216impl std::fmt::Debug for GroupEpochSecrets {
1217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1218 f.write_str("GroupEpochSecrets { *** }")
1219 }
1220}
1221
1222#[cfg(not(any(test, feature = "test-utils")))]
1223impl PartialEq for GroupEpochSecrets {
1224 fn eq(&self, _other: &Self) -> bool {
1225 false
1226 }
1227}
1228
1229impl GroupEpochSecrets {
1230 pub(crate) fn init_secret(&self) -> &InitSecret {
1232 &self.init_secret
1233 }
1234
1235 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1237 &self.epoch_authenticator
1238 }
1239
1240 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1242 &self.exporter_secret
1243 }
1244
1245 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1247 &self.external_secret
1248 }
1249
1250 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1252 &self.resumption_psk
1253 }
1254}