1use std::collections::{HashMap, HashSet};
2
3use core_crypto_keystore::{
4 connection::FetchFromDatabase,
5 entities::{EntityFindParams, StoredEncryptionKeyPair, StoredHpkePrivateKey, StoredKeypackage},
6};
7use mls_crypto_provider::{Database, MlsCryptoProvider};
8use openmls::prelude::{
9 Credential as MlsCredential, CredentialWithKey, CryptoConfig, KeyPackage, KeyPackageRef, Lifetime,
10};
11use openmls_traits::OpenMlsCryptoProvider;
12use tls_codec::{Deserialize, Serialize};
13
14use super::{Error, Result};
15use crate::{
16 Ciphersuite, Credential, CredentialType, KeystoreError, MlsConversationConfiguration, MlsError, Session,
17 mls::session::SessionInner,
18};
19
20#[cfg(not(test))]
22pub const INITIAL_KEYING_MATERIAL_COUNT: usize = 100;
23#[cfg(test)]
25pub const INITIAL_KEYING_MATERIAL_COUNT: usize = 10;
26
27pub(crate) const KEYPACKAGE_DEFAULT_LIFETIME: std::time::Duration =
29 std::time::Duration::from_secs(60 * 60 * 24 * 28 * 3); impl Session {
32 pub async fn generate_one_keypackage_from_credential(
40 &self,
41 backend: &MlsCryptoProvider,
42 cs: Ciphersuite,
43 cb: &Credential,
44 ) -> Result<KeyPackage> {
45 let guard = self.inner.read().await;
46 let SessionInner {
47 keypackage_lifetime, ..
48 } = guard.as_ref().ok_or(Error::MlsNotInitialized)?;
49
50 let keypackage = KeyPackage::builder()
51 .leaf_node_capabilities(MlsConversationConfiguration::default_leaf_capabilities())
52 .key_package_lifetime(Lifetime::new(keypackage_lifetime.as_secs()))
53 .build(
54 CryptoConfig {
55 ciphersuite: cs.into(),
56 version: openmls::versions::ProtocolVersion::default(),
57 },
58 backend,
59 &cb.signature_key_pair,
60 CredentialWithKey {
61 credential: cb.mls_credential.clone(),
62 signature_key: cb.signature_key_pair.public().into(),
63 },
64 )
65 .await
66 .map_err(KeystoreError::wrap("building keypackage"))?;
67
68 Ok(keypackage)
69 }
70
71 pub async fn request_key_packages(
82 &self,
83 count: usize,
84 ciphersuite: Ciphersuite,
85 credential_type: CredentialType,
86 backend: &MlsCryptoProvider,
87 ) -> Result<Vec<KeyPackage>> {
88 self.prune_keypackages(backend, std::iter::empty()).await?;
90 use core_crypto_keystore::CryptoKeystoreMls as _;
91
92 let mut existing_kps = backend
93 .key_store()
94 .mls_fetch_keypackages::<KeyPackage>(count as u32)
95 .await.map_err(KeystoreError::wrap("fetching mls keypackages"))?
96 .into_iter()
97 .filter(|kp|
99 ciphersuite == kp.ciphersuite() && credential_type == kp.leaf_node().credential().credential_type() )
100 .collect::<Vec<_>>();
101
102 let kpb_count = existing_kps.len();
103 let mut kps = if count > kpb_count {
104 let to_generate = count - kpb_count;
105 let cb = self
106 .find_most_recent_credential(ciphersuite.signature_algorithm(), credential_type)
107 .await?;
108 self.generate_new_keypackages(backend, ciphersuite, &cb, to_generate)
109 .await?
110 } else {
111 vec![]
112 };
113
114 existing_kps.reverse();
115
116 kps.append(&mut existing_kps);
117 Ok(kps)
118 }
119
120 pub(crate) async fn generate_new_keypackages(
121 &self,
122 backend: &MlsCryptoProvider,
123 ciphersuite: Ciphersuite,
124 cb: &Credential,
125 count: usize,
126 ) -> Result<Vec<KeyPackage>> {
127 let mut kps = Vec::with_capacity(count);
128
129 for _ in 0..count {
130 let kp = self
131 .generate_one_keypackage_from_credential(backend, ciphersuite, cb)
132 .await?;
133 kps.push(kp);
134 }
135
136 Ok(kps)
137 }
138
139 pub async fn valid_keypackages_count(
141 &self,
142 backend: &MlsCryptoProvider,
143 ciphersuite: Ciphersuite,
144 credential_type: CredentialType,
145 ) -> Result<usize> {
146 let kps: Vec<StoredKeypackage> = backend
147 .key_store()
148 .find_all(EntityFindParams::default())
149 .await
150 .map_err(KeystoreError::wrap("finding all key packages"))?;
151
152 let mut valid_count = 0;
153 for kp in kps
154 .into_iter()
155 .map(|kp| core_crypto_keystore::deser::<KeyPackage>(&kp.keypackage))
156 .filter(|kp_result| {
158 kp_result.as_ref().ok().is_none_or(|key_package| ciphersuite == key_package.ciphersuite() && credential_type == key_package.leaf_node().credential().credential_type())
159 })
160 {
161 let kp = kp.map_err(KeystoreError::wrap("counting valid keypackages"))?;
162 if !Self::is_mls_keypackage_expired(&kp) {
163 valid_count += 1;
164 }
165 }
166
167 Ok(valid_count)
168 }
169
170 fn is_mls_keypackage_expired(kp: &KeyPackage) -> bool {
173 let Some(lifetime) = kp.leaf_node().life_time() else {
174 return false;
175 };
176
177 !(lifetime.has_acceptable_range() && lifetime.is_valid())
178 }
179
180 pub async fn prune_keypackages(
186 &self,
187 backend: &MlsCryptoProvider,
188 refs: impl IntoIterator<Item = KeyPackageRef>,
189 ) -> Result<()> {
190 let keystore = backend.keystore();
191 let kps = self.find_all_keypackages(&keystore).await?;
192 let _ = self._prune_keypackages(&kps, &keystore, refs).await?;
193 Ok(())
194 }
195
196 pub(crate) async fn prune_keypackages_and_credential(
197 &mut self,
198 backend: &MlsCryptoProvider,
199 refs: impl IntoIterator<Item = KeyPackageRef>,
200 ) -> Result<()> {
201 let mut guard = self.inner.write().await;
202 let SessionInner { identities, .. } = guard.as_mut().ok_or(Error::MlsNotInitialized)?;
203
204 let keystore = backend.key_store();
205 let kps = self.find_all_keypackages(keystore).await?;
206 let kp_to_delete = self._prune_keypackages(&kps, keystore, refs).await?;
207
208 let mut grouped_kps = HashMap::<Vec<u8>, Vec<KeyPackageRef>>::new();
210 for (_, kp) in &kps {
211 let cred = kp
212 .leaf_node()
213 .credential()
214 .tls_serialize_detached()
215 .map_err(Error::tls_serialize("keypackage"))?;
216 let kp_ref = kp
217 .hash_ref(backend.crypto())
218 .map_err(MlsError::wrap("computing keypackage hashref"))?;
219 grouped_kps.entry(cred).or_default().push(kp_ref);
220 }
221
222 for (credential, kps) in &grouped_kps {
223 let all_to_delete = kps.iter().all(|kpr| kp_to_delete.contains(&kpr.as_slice()));
225 if all_to_delete {
226 backend
228 .keystore()
229 .cred_delete_by_credential(credential.clone())
230 .await
231 .map_err(KeystoreError::wrap("deleting credential"))?;
232 let credential = MlsCredential::tls_deserialize(&mut credential.as_slice())
233 .map_err(Error::tls_deserialize("credential"))?;
234 identities.remove_by_mls_credential(&credential);
235 }
236 }
237
238 Ok(())
239 }
240
241 async fn _prune_keypackages<'a>(
246 &self,
247 kps: &'a [(StoredKeypackage, KeyPackage)],
248 keystore: &Database,
249 refs: impl IntoIterator<Item = KeyPackageRef>,
250 ) -> Result<HashSet<&'a [u8]>, Error> {
251 let refs = refs
252 .into_iter()
253 .map(|kp| {
254 kp.as_slice().to_owned()
264 })
265 .collect::<HashSet<_>>();
266
267 let kp_to_delete = kps.iter().filter_map(|(store_kp, kp)| {
268 let is_expired = Self::is_mls_keypackage_expired(kp);
269 let to_delete = is_expired || refs.contains(store_kp.keypackage_ref.as_slice());
270 to_delete.then_some((kp, &store_kp.keypackage_ref))
271 });
272
273 for (kp, kp_ref) in kp_to_delete.clone() {
275 keystore
276 .remove::<StoredKeypackage, &[u8]>(kp_ref.as_slice())
277 .await
278 .map_err(KeystoreError::wrap("removing key package from keystore"))?;
279 keystore
280 .remove::<StoredHpkePrivateKey, &[u8]>(kp.hpke_init_key().as_slice())
281 .await
282 .map_err(KeystoreError::wrap("removing private key from keystore"))?;
283 keystore
284 .remove::<StoredEncryptionKeyPair, &[u8]>(kp.leaf_node().encryption_key().as_slice())
285 .await
286 .map_err(KeystoreError::wrap("removing encryption keypair from keystore"))?;
287 }
288
289 Ok(kp_to_delete.map(|(_, kpref)| kpref.as_slice()).collect())
290 }
291
292 pub(super) async fn find_all_keypackages(
293 &self,
294 keystore: &Database,
295 ) -> Result<Vec<(StoredKeypackage, KeyPackage)>> {
296 let kps: Vec<StoredKeypackage> = keystore
297 .find_all(EntityFindParams::default())
298 .await
299 .map_err(KeystoreError::wrap("finding all keypackages"))?;
300
301 let kps = kps
302 .into_iter()
303 .map(|raw_kp| -> Result<_> {
304 let kp = core_crypto_keystore::deser::<KeyPackage>(&raw_kp.keypackage)
305 .map_err(KeystoreError::wrap("deserializing keypackage"))?;
306 Ok((raw_kp, kp))
307 })
308 .collect::<Result<Vec<_>, _>>()?;
309
310 Ok(kps)
311 }
312
313 #[cfg(test)]
316 pub async fn set_keypackage_lifetime(&self, duration: std::time::Duration) -> Result<()> {
317 match &mut *self.inner.write().await {
318 None => Err(Error::MlsNotInitialized),
319 Some(SessionInner {
320 keypackage_lifetime, ..
321 }) => {
322 *keypackage_lifetime = duration;
323 Ok(())
324 }
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use core_crypto_keystore::{ConnectionType, DatabaseKey};
332 use mls_crypto_provider::{Database, MlsCryptoProvider};
333 use openmls::prelude::{KeyPackage, KeyPackageIn, KeyPackageRef, ProtocolVersion};
334 use openmls_traits::{OpenMlsCryptoProvider, types::VerifiableCiphersuite};
335
336 use super::Session;
337 use crate::{
338 MlsConversationConfiguration,
339 e2e_identity::enrollment::test_utils::{e2ei_enrollment, init_activation_or_rotation, noop_restore},
340 test_utils::*,
341 };
342
343 #[apply(all_cred_cipher)]
344 async fn can_assess_keypackage_expiration(case: TestContext) {
345 let [session_context] = case.sessions().await;
346 let (cs, ct) = (case.ciphersuite(), case.credential_type);
347 let key = DatabaseKey::generate();
348 let database = Database::open(ConnectionType::InMemory, &key).await.unwrap();
349 let backend = MlsCryptoProvider::new(database);
350 let x509_test_chain = if case.is_x509() {
351 let x509_test_chain = crate::test_utils::x509::X509TestChain::init_empty(case.signature_scheme());
352 x509_test_chain.register_with_provider(&backend).await;
353 Some(x509_test_chain)
354 } else {
355 None
356 };
357
358 backend.new_transaction().await.unwrap();
359 let session = session_context.session;
360 session
361 .random_generate(
362 &case,
363 x509_test_chain.as_ref().map(|chain| chain.find_local_intermediate_ca()),
364 )
365 .await
366 .unwrap();
367
368 let kp_std_exp = session.generate_one_keypackage(&backend, cs, ct).await.unwrap();
370 assert!(!Session::is_mls_keypackage_expired(&kp_std_exp));
371
372 session
374 .set_keypackage_lifetime(std::time::Duration::from_secs(1))
375 .await
376 .unwrap();
377 let kp_1s_exp = session.generate_one_keypackage(&backend, cs, ct).await.unwrap();
378 smol::Timer::after(std::time::Duration::from_secs(2)).await;
380 assert!(Session::is_mls_keypackage_expired(&kp_1s_exp));
381 }
382
383 #[apply(all_cred_cipher)]
384 async fn requesting_x509_key_packages_after_basic(case: TestContext) {
385 if !case.is_basic() {
387 return;
388 }
389
390 let [session_context] = case.sessions_basic_with_pki_env().await;
391 Box::pin(async move {
392 let signature_scheme = case.signature_scheme();
393 let cipher_suite = case.ciphersuite();
394
395 let _basic_key_packages = session_context
397 .transaction
398 .get_or_create_client_keypackages(cipher_suite, CredentialType::Basic, 5)
399 .await
400 .unwrap();
401
402 let test_chain = session_context.x509_chain_unchecked();
404
405 let (mut enrollment, cert_chain) = e2ei_enrollment(
406 &session_context,
407 &case,
408 test_chain,
409 None,
410 false,
411 init_activation_or_rotation,
412 noop_restore,
413 )
414 .await
415 .unwrap();
416
417 let _rotate_bundle = session_context
418 .transaction
419 .save_x509_credential(&mut enrollment, cert_chain)
420 .await
421 .unwrap();
422
423 assert!(
425 session_context
426 .transaction
427 .e2ei_is_enabled(signature_scheme)
428 .await
429 .unwrap()
430 );
431
432 let x509_key_packages = session_context
434 .transaction
435 .get_or_create_client_keypackages(cipher_suite, CredentialType::X509, 5)
436 .await
437 .unwrap();
438
439 assert!(
441 x509_key_packages
442 .iter()
443 .all(|kp| CredentialType::X509 == kp.leaf_node().credential().credential_type())
444 );
445 })
446 .await
447 }
448
449 #[apply(all_cred_cipher)]
450 async fn generates_correct_number_of_kpbs(case: TestContext) {
451 let [cc] = case.sessions().await;
452 Box::pin(async move {
453 const N: usize = 2;
454 const COUNT: usize = 109;
455
456 let init = cc.transaction.count_entities().await;
457 assert_eq!(init.key_package, 0);
458 assert_eq!(init.encryption_keypair, 0);
459 assert_eq!(init.hpke_private_key, 0);
460 assert_eq!(init.credential, 1);
461
462 let transactional_provider = cc.transaction.mls_provider().await.unwrap();
465 let crypto_provider = transactional_provider.crypto();
466 let mut pinned_kp = None;
467
468 let mut prev_kps: Option<Vec<KeyPackage>> = None;
469 for _ in 0..N {
470 let mut kps = cc
471 .transaction
472 .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, COUNT + 1)
473 .await
474 .unwrap();
475
476 pinned_kp = Some(kps.pop().unwrap());
478
479 assert_eq!(kps.len(), COUNT);
480 let after_creation = cc.transaction.count_entities().await;
481 assert_eq!(after_creation.key_package, COUNT + 1);
482 assert_eq!(after_creation.encryption_keypair, COUNT + 1);
483 assert_eq!(after_creation.hpke_private_key, COUNT + 1);
484 assert_eq!(after_creation.credential, 1);
485
486 let kpbs_refs = kps
487 .iter()
488 .map(|kp| kp.hash_ref(crypto_provider).unwrap())
489 .collect::<Vec<KeyPackageRef>>();
490
491 if let Some(pkpbs) = prev_kps.replace(kps) {
492 let pkpbs_refs = pkpbs
493 .into_iter()
494 .map(|kpb| kpb.hash_ref(crypto_provider).unwrap())
495 .collect::<Vec<KeyPackageRef>>();
496
497 let has_duplicates = kpbs_refs.iter().any(|href| pkpbs_refs.contains(href));
498 assert!(!has_duplicates);
500 }
501 cc.transaction.delete_keypackages(kpbs_refs).await.unwrap();
502 }
503
504 let count = cc
505 .transaction
506 .client_valid_key_packages_count(case.ciphersuite(), case.credential_type)
507 .await
508 .unwrap();
509 assert_eq!(count, 1);
510
511 let pinned_kpr = pinned_kp.unwrap().hash_ref(crypto_provider).unwrap();
512 cc.transaction.delete_keypackages([pinned_kpr]).await.unwrap();
513 let count = cc
514 .transaction
515 .client_valid_key_packages_count(case.ciphersuite(), case.credential_type)
516 .await
517 .unwrap();
518 assert_eq!(count, 0);
519 let after_delete = cc.transaction.count_entities().await;
520 assert_eq!(after_delete.key_package, 0);
521 assert_eq!(after_delete.encryption_keypair, 0);
522 assert_eq!(after_delete.hpke_private_key, 0);
523 assert_eq!(after_delete.credential, 0);
524 })
525 .await
526 }
527
528 #[apply(all_cred_cipher)]
529 async fn automatically_prunes_lifetime_expired_keypackages(case: TestContext) {
530 let [session_context] = case.sessions().await;
531 const UNEXPIRED_COUNT: usize = 125;
532 const EXPIRED_COUNT: usize = 200;
533 let key = DatabaseKey::generate();
534 let key_store = Database::open(ConnectionType::InMemory, &key).await.unwrap();
535 let backend = MlsCryptoProvider::new(key_store);
536 let x509_test_chain = if case.is_x509() {
537 let x509_test_chain = crate::test_utils::x509::X509TestChain::init_empty(case.signature_scheme());
538 x509_test_chain.register_with_provider(&backend).await;
539 Some(x509_test_chain)
540 } else {
541 None
542 };
543 backend.new_transaction().await.unwrap();
544 let session = session_context.session().await;
545 session
546 .random_generate(
547 &case,
548 x509_test_chain.as_ref().map(|chain| chain.find_local_intermediate_ca()),
549 )
550 .await
551 .unwrap();
552
553 let unexpired_kpbs = session
555 .request_key_packages(UNEXPIRED_COUNT, case.ciphersuite(), case.credential_type, &backend)
556 .await
557 .unwrap();
558 let len = session
559 .valid_keypackages_count(&backend, case.ciphersuite(), case.credential_type)
560 .await
561 .unwrap();
562 assert_eq!(len, unexpired_kpbs.len());
563 assert_eq!(len, UNEXPIRED_COUNT);
564
565 session
567 .set_keypackage_lifetime(std::time::Duration::from_secs(10))
568 .await
569 .unwrap();
570
571 let partially_expired_kpbs = session
573 .request_key_packages(EXPIRED_COUNT, case.ciphersuite(), case.credential_type, &backend)
574 .await
575 .unwrap();
576 assert_eq!(partially_expired_kpbs.len(), EXPIRED_COUNT);
577
578 smol::Timer::after(std::time::Duration::from_secs(10)).await;
580
581 let fresh_kpbs = session
584 .request_key_packages(EXPIRED_COUNT, case.ciphersuite(), case.credential_type, &backend)
585 .await
586 .unwrap();
587 let len = session
588 .valid_keypackages_count(&backend, case.ciphersuite(), case.credential_type)
589 .await
590 .unwrap();
591 assert_eq!(len, fresh_kpbs.len());
592 assert_eq!(len, EXPIRED_COUNT);
593
594 let (unexpired_match, expired_match) =
596 fresh_kpbs
597 .iter()
598 .fold((0usize, 0usize), |(mut unexpired_match, mut expired_match), fresh| {
599 if unexpired_kpbs.iter().any(|kp| kp == fresh) {
600 unexpired_match += 1;
601 } else if partially_expired_kpbs.iter().any(|kpb| kpb == fresh) {
602 expired_match += 1;
603 }
604
605 (unexpired_match, expired_match)
606 });
607
608 assert_eq!(unexpired_match, UNEXPIRED_COUNT);
610 assert_eq!(expired_match, 0);
611 }
612
613 #[apply(all_cred_cipher)]
614 async fn new_keypackage_has_correct_extensions(case: TestContext) {
615 let [cc] = case.sessions().await;
616 Box::pin(async move {
617 let kps = cc
618 .transaction
619 .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, 1)
620 .await
621 .unwrap();
622 let kp = kps.first().unwrap();
623
624 let _ = KeyPackageIn::from(kp.clone())
626 .standalone_validate(
627 &cc.transaction.mls_provider().await.unwrap(),
628 ProtocolVersion::Mls10,
629 true,
630 )
631 .await
632 .unwrap();
633
634 assert!(kp.extensions().is_empty());
636
637 assert_eq!(kp.leaf_node().capabilities().versions(), &[ProtocolVersion::Mls10]);
638 assert_eq!(
639 kp.leaf_node().capabilities().ciphersuites().to_vec(),
640 MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
641 .iter()
642 .map(|c| VerifiableCiphersuite::from(*c))
643 .collect::<Vec<_>>()
644 );
645 assert!(kp.leaf_node().capabilities().proposals().is_empty());
646 assert!(kp.leaf_node().capabilities().extensions().is_empty());
647 assert_eq!(
648 kp.leaf_node().capabilities().credentials(),
649 MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
650 );
651 })
652 .await
653 }
654}