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-08")]
136mod pprf;
137pub mod psk;
138
139#[cfg(feature = "extensions-draft-08")]
140pub use pprf::PprfError;
141
142#[cfg(feature = "extensions-draft-08")]
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))]
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-08")]
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-08")]
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 pub(crate) fn derive_exported_secret(
747 &self,
748 ciphersuite: Ciphersuite,
749 crypto: &impl OpenMlsCrypto,
750 label: &str,
751 context: &[u8],
752 key_length: usize,
753 ) -> Result<Vec<u8>, CryptoError> {
754 let context_hash = &crypto.hash(ciphersuite.hash_algorithm(), context)?;
755 Ok(self
756 .secret
757 .derive_secret(crypto, ciphersuite, label)?
758 .kdf_expand_label(crypto, ciphersuite, "exported", context_hash, key_length)?
759 .as_slice()
760 .to_vec())
761 }
762}
763
764#[cfg(feature = "extensions-draft-08")]
768#[derive(Debug, Serialize, Deserialize, Clone)]
769#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq))]
770pub struct ApplicationExportSecret {
771 secret: Secret,
772}
773
774#[cfg(feature = "extensions-draft-08")]
775impl ApplicationExportSecret {
776 fn new(
778 crypto: &impl OpenMlsCrypto,
779 ciphersuite: Ciphersuite,
780 epoch_secret: &EpochSecret,
781 ) -> Result<Self, CryptoError> {
782 let secret =
783 epoch_secret
784 .secret
785 .derive_secret(crypto, ciphersuite, "application_export")?;
786 Ok(ApplicationExportSecret { secret })
787 }
788}
789
790#[derive(Debug, Serialize, Deserialize)]
792#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
793pub(crate) struct ExternalSecret {
794 secret: Secret,
795}
796
797impl ExternalSecret {
798 fn new(
800 crypto: &impl OpenMlsCrypto,
801 ciphersuite: Ciphersuite,
802 epoch_secret: &EpochSecret,
803 ) -> Result<Self, CryptoError> {
804 let secret = epoch_secret
805 .secret
806 .derive_secret(crypto, ciphersuite, "external")?;
807 Ok(Self { secret })
808 }
809
810 pub(crate) fn derive_external_keypair(
812 &self,
813 crypto: &impl OpenMlsCrypto,
814 ciphersuite: Ciphersuite,
815 ) -> Result<HpkeKeyPair, CryptoError> {
816 crypto.derive_hpke_keypair(ciphersuite.hpke_config(), self.secret.as_slice())
817 }
818
819 #[cfg(any(feature = "test-utils", test))]
820 pub(crate) fn as_slice(&self) -> &[u8] {
821 self.secret.as_slice()
822 }
823}
824
825#[derive(Debug, Serialize, Deserialize)]
827#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
828pub(crate) struct ConfirmationKey {
829 secret: Secret,
830}
831
832impl ConfirmationKey {
833 fn new(
835 crypto: &impl OpenMlsCrypto,
836 ciphersuite: Ciphersuite,
837 epoch_secret: &EpochSecret,
838 ) -> Result<Self, CryptoError> {
839 log::debug!("Computing confirmation key.");
840 log_crypto!(
841 trace,
842 " epoch_secret {:x?}",
843 epoch_secret.secret.as_slice()
844 );
845 let secret = epoch_secret
846 .secret
847 .derive_secret(crypto, ciphersuite, "confirm")?;
848 Ok(Self { secret })
849 }
850
851 pub(crate) fn tag(
860 &self,
861 crypto: &impl OpenMlsCrypto,
862 ciphersuite: Ciphersuite,
863 confirmed_transcript_hash: &[u8],
864 ) -> Result<ConfirmationTag, CryptoError> {
865 log::debug!("Computing confirmation tag.");
866 log_crypto!(trace, " confirmation key {:x?}", self.secret.as_slice());
867 log_crypto!(trace, " transcript hash {:x?}", confirmed_transcript_hash);
868 Ok(ConfirmationTag(Mac::new(
869 crypto,
870 ciphersuite,
871 &self.secret,
872 confirmed_transcript_hash,
873 )?))
874 }
875}
876
877#[cfg(test)]
878impl ConfirmationKey {
879 pub(crate) fn from_secret(secret: Secret) -> Self {
880 Self { secret }
881 }
882}
883
884#[cfg(any(feature = "test-utils", test))]
885impl ConfirmationKey {
886 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
887 Self {
888 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
889 }
890 }
891
892 pub(crate) fn as_slice(&self) -> &[u8] {
893 self.secret.as_slice()
894 }
895}
896
897#[derive(Debug, Serialize, Deserialize)]
899#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
900pub(crate) struct MembershipKey {
901 secret: Secret,
902}
903
904impl MembershipKey {
905 fn new(
907 crypto: &impl OpenMlsCrypto,
908 ciphersuite: Ciphersuite,
909 epoch_secret: &EpochSecret,
910 ) -> Result<Self, CryptoError> {
911 let secret = epoch_secret
912 .secret
913 .derive_secret(crypto, ciphersuite, "membership")?;
914 Ok(Self { secret })
915 }
916
917 pub(crate) fn tag_message(
925 &self,
926 crypto: &impl OpenMlsCrypto,
927 ciphersuite: Ciphersuite,
928 tbm_payload: AuthenticatedContentTbm,
929 ) -> Result<MembershipTag, LibraryError> {
930 Ok(MembershipTag(
931 Mac::new(
932 crypto,
933 ciphersuite,
934 &self.secret,
935 &tbm_payload
936 .into_bytes()
937 .map_err(LibraryError::missing_bound_check)?,
938 )
939 .map_err(LibraryError::unexpected_crypto_error)?,
940 ))
941 }
942
943 #[cfg(any(feature = "test-utils", test))]
944 pub(crate) fn from_secret(secret: Secret) -> Self {
945 Self { secret }
946 }
947
948 #[cfg(any(feature = "test-utils", test))]
949 pub(crate) fn as_slice(&self) -> &[u8] {
950 self.secret.as_slice()
951 }
952
953 #[cfg(any(feature = "test-utils", test))]
954 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
955 Self {
956 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
957 }
958 }
959}
960
961fn ciphertext_sample(ciphersuite: Ciphersuite, ciphertext: &[u8]) -> &[u8] {
963 let sample_length = ciphersuite.hash_length();
964 log::debug!("Getting ciphertext sample of length {sample_length:?}");
965 if ciphertext.len() <= sample_length {
966 ciphertext
967 } else {
968 &ciphertext[0..sample_length]
969 }
970}
971
972#[derive(Serialize, Deserialize)]
974#[cfg_attr(
975 any(feature = "test-utils", feature = "crypto-debug", test),
976 derive(Debug, Clone, PartialEq)
977)]
978pub(crate) struct SenderDataSecret {
979 secret: Secret,
980}
981
982impl SenderDataSecret {
983 fn new(
985 crypto: &impl OpenMlsCrypto,
986 ciphersuite: Ciphersuite,
987 epoch_secret: &EpochSecret,
988 ) -> Result<Self, CryptoError> {
989 let secret = epoch_secret
990 .secret
991 .derive_secret(crypto, ciphersuite, "sender data")?;
992 Ok(SenderDataSecret { secret })
993 }
994
995 pub(crate) fn derive_aead_key(
997 &self,
998 crypto: &impl OpenMlsCrypto,
999 ciphersuite: Ciphersuite,
1000 ciphertext: &[u8],
1001 ) -> Result<AeadKey, CryptoError> {
1002 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
1003 log::debug!("SenderDataSecret::derive_aead_key ciphertext sample: {ciphertext_sample:x?}");
1004 let secret = self.secret.kdf_expand_label(
1005 crypto,
1006 ciphersuite,
1007 "key",
1008 ciphertext_sample,
1009 ciphersuite.aead_key_length(),
1010 )?;
1011 Ok(AeadKey::from_secret(secret, ciphersuite))
1012 }
1013
1014 pub(crate) fn derive_aead_nonce(
1016 &self,
1017 ciphersuite: Ciphersuite,
1018 crypto: &impl OpenMlsCrypto,
1019 ciphertext: &[u8],
1020 ) -> Result<AeadNonce, CryptoError> {
1021 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
1022 log::debug!(
1023 "SenderDataSecret::derive_aead_nonce ciphertext sample: {ciphertext_sample:x?}"
1024 );
1025 let nonce_secret = self.secret.kdf_expand_label(
1026 crypto,
1027 ciphersuite,
1028 "nonce",
1029 ciphertext_sample,
1030 ciphersuite.aead_nonce_length(),
1031 )?;
1032 Ok(AeadNonce::from_secret(nonce_secret))
1033 }
1034
1035 #[cfg(any(feature = "test-utils", test))]
1036 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
1037 Self {
1038 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
1039 }
1040 }
1041
1042 #[cfg(any(feature = "test-utils", test))]
1043 pub(crate) fn as_slice(&self) -> &[u8] {
1044 self.secret.as_slice()
1045 }
1046
1047 #[cfg(any(feature = "test-utils", test))]
1048 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1050 Self {
1051 secret: Secret::from_slice(bytes),
1052 }
1053 }
1054}
1055
1056pub(crate) struct EpochSecrets {
1072 init_secret: InitSecret,
1073 sender_data_secret: SenderDataSecret,
1074 encryption_secret: EncryptionSecret,
1075 exporter_secret: ExporterSecret,
1076 epoch_authenticator: EpochAuthenticator,
1077 external_secret: ExternalSecret,
1078 confirmation_key: ConfirmationKey,
1079 membership_key: MembershipKey,
1080 resumption_psk: ResumptionPskSecret,
1081}
1082
1083impl std::fmt::Debug for EpochSecrets {
1084 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1085 f.write_str("EpochSecrets { *** }")
1086 }
1087}
1088
1089#[cfg(not(test))]
1090impl PartialEq for EpochSecrets {
1091 fn eq(&self, _other: &Self) -> bool {
1092 false
1093 }
1094}
1095
1096#[cfg(test)]
1098impl PartialEq for EpochSecrets {
1099 fn eq(&self, other: &Self) -> bool {
1100 self.sender_data_secret == other.sender_data_secret
1101 && self.exporter_secret == other.exporter_secret
1102 && self.epoch_authenticator == other.epoch_authenticator
1103 && self.external_secret == other.external_secret
1104 && self.confirmation_key == other.confirmation_key
1105 && self.membership_key == other.membership_key
1106 && self.resumption_psk == other.resumption_psk
1107 }
1108}
1109
1110impl EpochSecrets {
1111 #[cfg(any(feature = "test-utils", test))]
1113 pub(crate) fn sender_data_secret(&self) -> &SenderDataSecret {
1114 &self.sender_data_secret
1115 }
1116
1117 pub(crate) fn confirmation_key(&self) -> &ConfirmationKey {
1119 &self.confirmation_key
1120 }
1121
1122 #[cfg(any(feature = "test-utils", test))]
1124 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1125 &self.epoch_authenticator
1126 }
1127
1128 #[cfg(any(feature = "test-utils", test))]
1130 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1131 &self.exporter_secret
1132 }
1133
1134 #[cfg(any(feature = "test-utils", test))]
1136 pub(crate) fn membership_key(&self) -> &MembershipKey {
1137 &self.membership_key
1138 }
1139
1140 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1142 &self.external_secret
1143 }
1144
1145 #[cfg(any(feature = "test-utils", test))]
1147 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1148 &self.resumption_psk
1149 }
1150
1151 #[cfg(any(feature = "test-utils", test))]
1153 pub(crate) fn init_secret(&self) -> &InitSecret {
1154 &self.init_secret
1155 }
1156
1157 #[cfg(any(feature = "test-utils", test))]
1159 pub(crate) fn encryption_secret(&self) -> &EncryptionSecret {
1160 &self.encryption_secret
1161 }
1162
1163 fn new(
1167 crypto: &impl OpenMlsCrypto,
1168 ciphersuite: Ciphersuite,
1169 epoch_secret: EpochSecret,
1170 ) -> Result<Self, CryptoError> {
1171 log::debug!("Computing EpochSecrets from epoch secret with {ciphersuite}");
1172 log_crypto!(
1173 trace,
1174 " epoch_secret: {:x?}",
1175 epoch_secret.secret.as_slice()
1176 );
1177 let sender_data_secret = SenderDataSecret::new(crypto, ciphersuite, &epoch_secret)?;
1178 let encryption_secret = EncryptionSecret::new(crypto, ciphersuite, &epoch_secret)?;
1179 let exporter_secret = ExporterSecret::new(crypto, ciphersuite, &epoch_secret)?;
1180 let epoch_authenticator = EpochAuthenticator::new(crypto, ciphersuite, &epoch_secret)?;
1181 let external_secret = ExternalSecret::new(crypto, ciphersuite, &epoch_secret)?;
1182 let confirmation_key = ConfirmationKey::new(crypto, ciphersuite, &epoch_secret)?;
1183 let membership_key = MembershipKey::new(crypto, ciphersuite, &epoch_secret)?;
1184 let resumption_psk = ResumptionPskSecret::new(crypto, ciphersuite, &epoch_secret)?;
1185
1186 log::trace!(" Computing init secret.");
1187 let init_secret = InitSecret::new(crypto, ciphersuite, epoch_secret)?;
1188
1189 Ok(EpochSecrets {
1190 init_secret,
1191 sender_data_secret,
1192 encryption_secret,
1193 exporter_secret,
1194 epoch_authenticator,
1195 external_secret,
1196 confirmation_key,
1197 membership_key,
1198 resumption_psk,
1199 })
1200 }
1201
1202 pub(crate) fn with_init_secret(
1207 crypto: &impl OpenMlsCrypto,
1208 ciphersuite: Ciphersuite,
1209 init_secret: InitSecret,
1210 ) -> Result<Self, CryptoError> {
1211 let epoch_secret = EpochSecret {
1212 secret: Secret::zero(ciphersuite),
1213 };
1214 let mut epoch_secrets = Self::new(crypto, ciphersuite, epoch_secret)?;
1215 epoch_secrets.init_secret = init_secret;
1216 Ok(epoch_secrets)
1217 }
1218
1219 pub(crate) fn split_secrets(
1224 self,
1225 serialized_context: Vec<u8>,
1226 treesize: TreeSize,
1227 own_index: LeafNodeIndex,
1228 ) -> (GroupEpochSecrets, MessageSecrets) {
1229 let secret_tree = self
1230 .encryption_secret
1231 .create_secret_tree(treesize, own_index);
1232 (
1233 GroupEpochSecrets {
1234 init_secret: self.init_secret,
1235 exporter_secret: self.exporter_secret,
1236 epoch_authenticator: self.epoch_authenticator,
1237 external_secret: self.external_secret,
1238 resumption_psk: self.resumption_psk,
1239 },
1240 MessageSecrets::new(
1241 self.sender_data_secret,
1242 self.membership_key,
1243 self.confirmation_key,
1244 serialized_context,
1245 secret_tree,
1246 ),
1247 )
1248 }
1249}
1250
1251#[derive(Serialize, Deserialize)]
1252#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
1253pub(crate) struct GroupEpochSecrets {
1254 init_secret: InitSecret,
1255 exporter_secret: ExporterSecret,
1256 epoch_authenticator: EpochAuthenticator,
1257 external_secret: ExternalSecret,
1258 resumption_psk: ResumptionPskSecret,
1259}
1260
1261impl std::fmt::Debug for GroupEpochSecrets {
1262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1263 f.write_str("GroupEpochSecrets { *** }")
1264 }
1265}
1266
1267#[cfg(not(any(test, feature = "test-utils")))]
1268impl PartialEq for GroupEpochSecrets {
1269 fn eq(&self, _other: &Self) -> bool {
1270 false
1271 }
1272}
1273
1274impl GroupEpochSecrets {
1275 pub(crate) fn init_secret(&self) -> &InitSecret {
1277 &self.init_secret
1278 }
1279
1280 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1282 &self.epoch_authenticator
1283 }
1284
1285 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1287 &self.exporter_secret
1288 }
1289
1290 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1292 &self.external_secret
1293 }
1294
1295 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1297 &self.resumption_psk
1298 }
1299}