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