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(
755 &self,
756 ciphersuite: Ciphersuite,
757 crypto: &impl OpenMlsCrypto,
758 label: &str,
759 context: &[u8],
760 key_length: usize,
761 ) -> Result<Vec<u8>, CryptoError> {
762 let context_hash = &crypto.hash(ciphersuite.hash_algorithm(), context)?;
763 Ok(self
764 .secret
765 .derive_secret(crypto, ciphersuite, label)?
766 .kdf_expand_label(crypto, ciphersuite, "exported", context_hash, key_length)?
767 .as_slice()
768 .to_vec())
769 }
770}
771
772#[cfg(feature = "extensions-draft")]
776#[derive(Debug, Serialize, Deserialize, Clone)]
777#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq))]
778pub struct ApplicationExportSecret {
779 secret: Secret,
780}
781
782#[cfg(feature = "extensions-draft")]
783impl ApplicationExportSecret {
784 fn new(
786 crypto: &impl OpenMlsCrypto,
787 ciphersuite: Ciphersuite,
788 epoch_secret: &EpochSecret,
789 ) -> Result<Self, CryptoError> {
790 let secret =
791 epoch_secret
792 .secret
793 .derive_secret(crypto, ciphersuite, "application_export")?;
794 Ok(ApplicationExportSecret { secret })
795 }
796}
797
798#[derive(Debug, Serialize, Deserialize)]
800#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
801pub(crate) struct ExternalSecret {
802 secret: Secret,
803}
804
805impl ExternalSecret {
806 fn new(
808 crypto: &impl OpenMlsCrypto,
809 ciphersuite: Ciphersuite,
810 epoch_secret: &EpochSecret,
811 ) -> Result<Self, CryptoError> {
812 let secret = epoch_secret
813 .secret
814 .derive_secret(crypto, ciphersuite, "external")?;
815 Ok(Self { secret })
816 }
817
818 pub(crate) fn derive_external_keypair(
820 &self,
821 crypto: &impl OpenMlsCrypto,
822 ciphersuite: Ciphersuite,
823 ) -> Result<HpkeKeyPair, CryptoError> {
824 crypto.derive_hpke_keypair(ciphersuite.hpke_config(), self.secret.as_slice())
825 }
826
827 #[cfg(any(feature = "test-utils", test))]
828 pub(crate) fn as_slice(&self) -> &[u8] {
829 self.secret.as_slice()
830 }
831}
832
833#[derive(Debug, Serialize, Deserialize)]
835#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
836pub(crate) struct ConfirmationKey {
837 secret: Secret,
838}
839
840impl ConfirmationKey {
841 fn new(
843 crypto: &impl OpenMlsCrypto,
844 ciphersuite: Ciphersuite,
845 epoch_secret: &EpochSecret,
846 ) -> Result<Self, CryptoError> {
847 log::debug!("Computing confirmation key.");
848 log_crypto!(
849 trace,
850 " epoch_secret {:x?}",
851 epoch_secret.secret.as_slice()
852 );
853 let secret = epoch_secret
854 .secret
855 .derive_secret(crypto, ciphersuite, "confirm")?;
856 Ok(Self { secret })
857 }
858
859 pub(crate) fn tag(
868 &self,
869 crypto: &impl OpenMlsCrypto,
870 ciphersuite: Ciphersuite,
871 confirmed_transcript_hash: &[u8],
872 ) -> Result<ConfirmationTag, CryptoError> {
873 log::debug!("Computing confirmation tag.");
874 log_crypto!(trace, " confirmation key {:x?}", self.secret.as_slice());
875 log_crypto!(trace, " transcript hash {:x?}", confirmed_transcript_hash);
876 Ok(ConfirmationTag(Mac::new(
877 crypto,
878 ciphersuite,
879 &self.secret,
880 confirmed_transcript_hash,
881 )?))
882 }
883}
884
885#[cfg(test)]
886impl ConfirmationKey {
887 pub(crate) fn from_secret(secret: Secret) -> Self {
888 Self { secret }
889 }
890}
891
892#[cfg(any(feature = "test-utils", test))]
893impl ConfirmationKey {
894 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
895 Self {
896 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
897 }
898 }
899
900 pub(crate) fn as_slice(&self) -> &[u8] {
901 self.secret.as_slice()
902 }
903}
904
905#[derive(Debug, Serialize, Deserialize)]
907#[cfg_attr(any(test, feature = "test-utils"), derive(PartialEq, Clone))]
908pub(crate) struct MembershipKey {
909 secret: Secret,
910}
911
912impl MembershipKey {
913 fn new(
915 crypto: &impl OpenMlsCrypto,
916 ciphersuite: Ciphersuite,
917 epoch_secret: &EpochSecret,
918 ) -> Result<Self, CryptoError> {
919 let secret = epoch_secret
920 .secret
921 .derive_secret(crypto, ciphersuite, "membership")?;
922 Ok(Self { secret })
923 }
924
925 pub(crate) fn tag_message(
933 &self,
934 crypto: &impl OpenMlsCrypto,
935 ciphersuite: Ciphersuite,
936 tbm_payload: AuthenticatedContentTbm,
937 ) -> Result<MembershipTag, LibraryError> {
938 Ok(MembershipTag(
939 Mac::new(
940 crypto,
941 ciphersuite,
942 &self.secret,
943 &tbm_payload
944 .into_bytes()
945 .map_err(LibraryError::missing_bound_check)?,
946 )
947 .map_err(LibraryError::unexpected_crypto_error)?,
948 ))
949 }
950
951 #[cfg(any(feature = "test-utils", test))]
952 pub(crate) fn from_secret(secret: Secret) -> Self {
953 Self { secret }
954 }
955
956 #[cfg(any(feature = "test-utils", test))]
957 pub(crate) fn as_slice(&self) -> &[u8] {
958 self.secret.as_slice()
959 }
960
961 #[cfg(any(feature = "test-utils", test))]
962 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
963 Self {
964 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
965 }
966 }
967}
968
969fn ciphertext_sample(ciphersuite: Ciphersuite, ciphertext: &[u8]) -> &[u8] {
971 let sample_length = ciphersuite.hash_length();
972 log::debug!("Getting ciphertext sample of length {sample_length:?}");
973 if ciphertext.len() <= sample_length {
974 ciphertext
975 } else {
976 &ciphertext[0..sample_length]
977 }
978}
979
980#[derive(Serialize, Deserialize)]
982#[cfg_attr(
983 any(feature = "test-utils", feature = "crypto-debug", test),
984 derive(Debug, Clone, PartialEq)
985)]
986pub(crate) struct SenderDataSecret {
987 secret: Secret,
988}
989
990impl SenderDataSecret {
991 fn new(
993 crypto: &impl OpenMlsCrypto,
994 ciphersuite: Ciphersuite,
995 epoch_secret: &EpochSecret,
996 ) -> Result<Self, CryptoError> {
997 let secret = epoch_secret
998 .secret
999 .derive_secret(crypto, ciphersuite, "sender data")?;
1000 Ok(SenderDataSecret { secret })
1001 }
1002
1003 pub(crate) fn derive_aead_key(
1005 &self,
1006 crypto: &impl OpenMlsCrypto,
1007 ciphersuite: Ciphersuite,
1008 ciphertext: &[u8],
1009 ) -> Result<AeadKey, CryptoError> {
1010 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
1011 log::debug!("SenderDataSecret::derive_aead_key ciphertext sample: {ciphertext_sample:x?}");
1012 let secret = self.secret.kdf_expand_label(
1013 crypto,
1014 ciphersuite,
1015 "key",
1016 ciphertext_sample,
1017 ciphersuite.aead_key_length(),
1018 )?;
1019 Ok(AeadKey::from_secret(secret, ciphersuite))
1020 }
1021
1022 pub(crate) fn derive_aead_nonce(
1024 &self,
1025 ciphersuite: Ciphersuite,
1026 crypto: &impl OpenMlsCrypto,
1027 ciphertext: &[u8],
1028 ) -> Result<AeadNonce, CryptoError> {
1029 let ciphertext_sample = ciphertext_sample(ciphersuite, ciphertext);
1030 log::debug!(
1031 "SenderDataSecret::derive_aead_nonce ciphertext sample: {ciphertext_sample:x?}"
1032 );
1033 let nonce_secret = self.secret.kdf_expand_label(
1034 crypto,
1035 ciphersuite,
1036 "nonce",
1037 ciphertext_sample,
1038 ciphersuite.aead_nonce_length(),
1039 )?;
1040 Ok(AeadNonce::from_secret(nonce_secret))
1041 }
1042
1043 #[cfg(any(feature = "test-utils", test))]
1044 pub(crate) fn random(ciphersuite: Ciphersuite, rng: &impl OpenMlsRand) -> Self {
1045 Self {
1046 secret: Secret::random(ciphersuite, rng).expect("Not enough randomness."),
1047 }
1048 }
1049
1050 #[cfg(any(feature = "test-utils", test))]
1051 pub(crate) fn as_slice(&self) -> &[u8] {
1052 self.secret.as_slice()
1053 }
1054
1055 #[cfg(any(feature = "test-utils", test))]
1056 pub(crate) fn from_slice(bytes: &[u8]) -> Self {
1058 Self {
1059 secret: Secret::from_slice(bytes),
1060 }
1061 }
1062}
1063
1064pub(crate) struct EpochSecrets {
1080 init_secret: InitSecret,
1081 sender_data_secret: SenderDataSecret,
1082 encryption_secret: EncryptionSecret,
1083 exporter_secret: ExporterSecret,
1084 epoch_authenticator: EpochAuthenticator,
1085 external_secret: ExternalSecret,
1086 confirmation_key: ConfirmationKey,
1087 membership_key: MembershipKey,
1088 resumption_psk: ResumptionPskSecret,
1089}
1090
1091impl std::fmt::Debug for EpochSecrets {
1092 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1093 f.write_str("EpochSecrets { *** }")
1094 }
1095}
1096
1097#[cfg(not(test))]
1098impl PartialEq for EpochSecrets {
1099 fn eq(&self, _other: &Self) -> bool {
1100 false
1101 }
1102}
1103
1104#[cfg(test)]
1106impl PartialEq for EpochSecrets {
1107 fn eq(&self, other: &Self) -> bool {
1108 self.sender_data_secret == other.sender_data_secret
1109 && self.exporter_secret == other.exporter_secret
1110 && self.epoch_authenticator == other.epoch_authenticator
1111 && self.external_secret == other.external_secret
1112 && self.confirmation_key == other.confirmation_key
1113 && self.membership_key == other.membership_key
1114 && self.resumption_psk == other.resumption_psk
1115 }
1116}
1117
1118impl EpochSecrets {
1119 #[cfg(any(feature = "test-utils", test))]
1121 pub(crate) fn sender_data_secret(&self) -> &SenderDataSecret {
1122 &self.sender_data_secret
1123 }
1124
1125 pub(crate) fn confirmation_key(&self) -> &ConfirmationKey {
1127 &self.confirmation_key
1128 }
1129
1130 #[cfg(any(feature = "test-utils", test))]
1132 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1133 &self.epoch_authenticator
1134 }
1135
1136 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1138 &self.exporter_secret
1139 }
1140
1141 #[cfg(any(feature = "test-utils", test))]
1143 pub(crate) fn membership_key(&self) -> &MembershipKey {
1144 &self.membership_key
1145 }
1146
1147 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1149 &self.external_secret
1150 }
1151
1152 #[cfg(any(feature = "test-utils", test))]
1154 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1155 &self.resumption_psk
1156 }
1157
1158 #[cfg(any(feature = "test-utils", test))]
1160 pub(crate) fn init_secret(&self) -> &InitSecret {
1161 &self.init_secret
1162 }
1163
1164 #[cfg(any(feature = "test-utils", test))]
1166 pub(crate) fn encryption_secret(&self) -> &EncryptionSecret {
1167 &self.encryption_secret
1168 }
1169
1170 fn new(
1174 crypto: &impl OpenMlsCrypto,
1175 ciphersuite: Ciphersuite,
1176 epoch_secret: EpochSecret,
1177 ) -> Result<Self, CryptoError> {
1178 log::debug!("Computing EpochSecrets from epoch secret with {ciphersuite}");
1179 log_crypto!(
1180 trace,
1181 " epoch_secret: {:x?}",
1182 epoch_secret.secret.as_slice()
1183 );
1184 let sender_data_secret = SenderDataSecret::new(crypto, ciphersuite, &epoch_secret)?;
1185 let encryption_secret = EncryptionSecret::new(crypto, ciphersuite, &epoch_secret)?;
1186 let exporter_secret = ExporterSecret::new(crypto, ciphersuite, &epoch_secret)?;
1187 let epoch_authenticator = EpochAuthenticator::new(crypto, ciphersuite, &epoch_secret)?;
1188 let external_secret = ExternalSecret::new(crypto, ciphersuite, &epoch_secret)?;
1189 let confirmation_key = ConfirmationKey::new(crypto, ciphersuite, &epoch_secret)?;
1190 let membership_key = MembershipKey::new(crypto, ciphersuite, &epoch_secret)?;
1191 let resumption_psk = ResumptionPskSecret::new(crypto, ciphersuite, &epoch_secret)?;
1192
1193 log::trace!(" Computing init secret.");
1194 let init_secret = InitSecret::new(crypto, ciphersuite, epoch_secret)?;
1195
1196 Ok(EpochSecrets {
1197 init_secret,
1198 sender_data_secret,
1199 encryption_secret,
1200 exporter_secret,
1201 epoch_authenticator,
1202 external_secret,
1203 confirmation_key,
1204 membership_key,
1205 resumption_psk,
1206 })
1207 }
1208
1209 pub(crate) fn with_init_secret(
1214 crypto: &impl OpenMlsCrypto,
1215 ciphersuite: Ciphersuite,
1216 init_secret: InitSecret,
1217 ) -> Result<Self, CryptoError> {
1218 let epoch_secret = EpochSecret {
1219 secret: Secret::zero(ciphersuite),
1220 };
1221 let mut epoch_secrets = Self::new(crypto, ciphersuite, epoch_secret)?;
1222 epoch_secrets.init_secret = init_secret;
1223 Ok(epoch_secrets)
1224 }
1225
1226 #[cfg(feature = "virtual-clients-draft")]
1233 pub(crate) fn from_epoch_secret(
1234 crypto: &impl OpenMlsCrypto,
1235 ciphersuite: Ciphersuite,
1236 epoch_secret: Secret,
1237 ) -> Result<Self, CryptoError> {
1238 let epoch_secret = EpochSecret {
1239 secret: epoch_secret,
1240 };
1241 Self::new(crypto, ciphersuite, epoch_secret)
1242 }
1243
1244 pub(crate) fn split_secrets(
1249 self,
1250 serialized_context: Vec<u8>,
1251 treesize: TreeSize,
1252 own_index: LeafNodeIndex,
1253 ) -> (GroupEpochSecrets, MessageSecrets) {
1254 let secret_tree = self
1255 .encryption_secret
1256 .create_secret_tree(treesize, own_index);
1257 (
1258 GroupEpochSecrets {
1259 init_secret: self.init_secret,
1260 exporter_secret: self.exporter_secret,
1261 epoch_authenticator: self.epoch_authenticator,
1262 external_secret: self.external_secret,
1263 resumption_psk: self.resumption_psk,
1264 },
1265 MessageSecrets::new(
1266 self.sender_data_secret,
1267 self.membership_key,
1268 self.confirmation_key,
1269 serialized_context,
1270 secret_tree,
1271 ),
1272 )
1273 }
1274}
1275
1276#[derive(Serialize, Deserialize)]
1277#[cfg_attr(any(test, feature = "test-utils"), derive(Clone, PartialEq))]
1278pub(crate) struct GroupEpochSecrets {
1279 init_secret: InitSecret,
1280 exporter_secret: ExporterSecret,
1281 epoch_authenticator: EpochAuthenticator,
1282 external_secret: ExternalSecret,
1283 resumption_psk: ResumptionPskSecret,
1284}
1285
1286impl std::fmt::Debug for GroupEpochSecrets {
1287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1288 f.write_str("GroupEpochSecrets { *** }")
1289 }
1290}
1291
1292#[cfg(not(any(test, feature = "test-utils")))]
1293impl PartialEq for GroupEpochSecrets {
1294 fn eq(&self, _other: &Self) -> bool {
1295 false
1296 }
1297}
1298
1299impl GroupEpochSecrets {
1300 pub(crate) fn init_secret(&self) -> &InitSecret {
1302 &self.init_secret
1303 }
1304
1305 pub(crate) fn epoch_authenticator(&self) -> &EpochAuthenticator {
1307 &self.epoch_authenticator
1308 }
1309
1310 pub(crate) fn exporter_secret(&self) -> &ExporterSecret {
1312 &self.exporter_secret
1313 }
1314
1315 pub(crate) fn external_secret(&self) -> &ExternalSecret {
1317 &self.external_secret
1318 }
1319
1320 pub(crate) fn resumption_psk(&self) -> &ResumptionPskSecret {
1322 &self.resumption_psk
1323 }
1324}