1use crate::{
2 CoreCrypto, Error, KeystoreError, LeafError, ProteusError, Result,
3 group_store::{GroupStore, GroupStoreEntity, GroupStoreValue},
4};
5use core_crypto_keystore::{
6 Database as CryptoKeystore,
7 connection::FetchFromDatabase,
8 entities::{ProteusIdentity, ProteusSession},
9};
10use proteus_wasm::{
11 keys::{IdentityKeyPair, PreKeyBundle},
12 message::Envelope,
13 session::Session,
14};
15use std::{collections::HashMap, sync::Arc};
16
17pub type SessionIdentifier = String;
19
20#[derive(Debug)]
22pub struct ProteusConversationSession {
23 pub(crate) identifier: SessionIdentifier,
24 pub(crate) session: Session<Arc<IdentityKeyPair>>,
25}
26
27impl ProteusConversationSession {
28 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
30 self.session
31 .encrypt(plaintext)
32 .and_then(|e| e.serialise())
33 .map_err(ProteusError::wrap("encrypting message for proteus session"))
34 .map_err(Into::into)
35 }
36
37 pub async fn decrypt(&mut self, store: &mut core_crypto_keystore::Database, ciphertext: &[u8]) -> Result<Vec<u8>> {
39 let envelope = Envelope::deserialise(ciphertext).map_err(ProteusError::wrap("deserializing envelope"))?;
40 self.session
41 .decrypt(store, &envelope)
42 .await
43 .map_err(ProteusError::wrap("decrypting message for proteus session"))
44 .map_err(Into::into)
45 }
46
47 pub fn identifier(&self) -> &str {
49 &self.identifier
50 }
51
52 pub fn fingerprint_local(&self) -> String {
54 self.session.local_identity().fingerprint()
55 }
56
57 pub fn fingerprint_remote(&self) -> String {
59 self.session.remote_identity().fingerprint()
60 }
61}
62
63#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
64#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
65impl GroupStoreEntity for ProteusConversationSession {
66 type RawStoreValue = core_crypto_keystore::entities::ProteusSession;
67 type IdentityType = Arc<proteus_wasm::keys::IdentityKeyPair>;
68
69 async fn fetch_from_id(
70 id: &[u8],
71 identity: Option<Self::IdentityType>,
72 keystore: &impl FetchFromDatabase,
73 ) -> crate::Result<Option<Self>> {
74 let result = keystore
75 .find::<Self::RawStoreValue>(id)
76 .await
77 .map_err(KeystoreError::wrap("finding raw group store entity by id"))?;
78 let Some(store_value) = result else {
79 return Ok(None);
80 };
81
82 let Some(identity) = identity else {
83 return Err(crate::Error::ProteusNotInitialized);
84 };
85
86 let session = proteus_wasm::session::Session::deserialise(identity, &store_value.session)
87 .map_err(ProteusError::wrap("deserializing session"))?;
88
89 Ok(Some(Self {
90 identifier: store_value.id.clone(),
91 session,
92 }))
93 }
94}
95
96impl CoreCrypto {
97 pub async fn proteus_session(
103 &self,
104 session_id: &str,
105 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
106 let mut mutex = self.proteus.lock().await;
107 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
108 let keystore = self.mls.crypto_provider.keystore();
109 proteus.session(session_id, &keystore).await
110 }
111
112 pub async fn proteus_session_exists(&self, session_id: &str) -> Result<bool> {
118 let mut mutex = self.proteus.lock().await;
119 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
120 let keystore = self.mls.crypto_provider.keystore();
121 Ok(proteus.session_exists(session_id, &keystore).await)
122 }
123
124 pub fn proteus_last_resort_prekey_id() -> u16 {
126 ProteusCentral::last_resort_prekey_id()
127 }
128
129 pub async fn proteus_fingerprint(&self) -> Result<String> {
135 let mutex = self.proteus.lock().await;
136 let proteus = mutex.as_ref().ok_or(Error::ProteusNotInitialized)?;
137 Ok(proteus.fingerprint())
138 }
139
140 pub async fn proteus_fingerprint_local(&self, session_id: &str) -> Result<String> {
146 let mut mutex = self.proteus.lock().await;
147 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
148 let keystore = self.mls.crypto_provider.keystore();
149 proteus.fingerprint_local(session_id, &keystore).await
150 }
151
152 pub async fn proteus_fingerprint_remote(&self, session_id: &str) -> Result<String> {
158 let mut mutex = self.proteus.lock().await;
159 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
160 let keystore = self.mls.crypto_provider.keystore();
161 proteus.fingerprint_remote(session_id, &keystore).await
162 }
163}
164
165#[derive(Debug)]
170pub struct ProteusCentral {
171 proteus_identity: Arc<IdentityKeyPair>,
172 proteus_sessions: GroupStore<ProteusConversationSession>,
173}
174
175impl ProteusCentral {
176 pub async fn try_new(keystore: &CryptoKeystore) -> Result<Self> {
178 let proteus_identity: Arc<IdentityKeyPair> = Arc::new(Self::load_or_create_identity(keystore).await?);
179 let proteus_sessions = Self::restore_sessions(keystore, &proteus_identity).await?;
180
181 Ok(Self {
182 proteus_identity,
183 proteus_sessions,
184 })
185 }
186
187 pub(crate) async fn reload_sessions(&mut self, keystore: &CryptoKeystore) -> Result<()> {
189 self.proteus_sessions = Self::restore_sessions(keystore, &self.proteus_identity).await?;
190 Ok(())
191 }
192
193 async fn load_or_create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
196 let Some(identity) = keystore
197 .find::<ProteusIdentity>(ProteusIdentity::ID)
198 .await
199 .map_err(KeystoreError::wrap("finding proteus identity"))?
200 else {
201 return Self::create_identity(keystore).await;
202 };
203
204 let sk = identity.sk_raw();
205 let pk = identity.pk_raw();
206
207 IdentityKeyPair::from_raw_key_pair(*sk, *pk)
209 .map_err(ProteusError::wrap("constructing identity keypair"))
210 .map_err(Into::into)
211 }
212
213 async fn create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
215 let kp = IdentityKeyPair::new();
216 let pk = kp.public_key.public_key.as_slice().to_vec();
217
218 let ks_identity = ProteusIdentity {
219 sk: kp.secret_key.to_keypair_bytes().into(),
220 pk,
221 };
222 keystore
223 .save(ks_identity)
224 .await
225 .map_err(KeystoreError::wrap("saving new proteus identity"))?;
226
227 Ok(kp)
228 }
229
230 async fn restore_sessions(
232 keystore: &core_crypto_keystore::Database,
233 identity: &Arc<IdentityKeyPair>,
234 ) -> Result<GroupStore<ProteusConversationSession>> {
235 let mut proteus_sessions = GroupStore::new_with_limit(crate::group_store::ITEM_LIMIT * 2);
236 for session in keystore
237 .find_all::<ProteusSession>(Default::default())
238 .await
239 .map_err(KeystoreError::wrap("finding all proteus sessions"))?
240 .into_iter()
241 {
242 let proteus_session = Session::deserialise(identity.clone(), &session.session)
243 .map_err(ProteusError::wrap("deserializing session"))?;
244
245 let identifier = session.id.clone();
246
247 let proteus_conversation = ProteusConversationSession {
248 identifier: identifier.clone(),
249 session: proteus_session,
250 };
251
252 if proteus_sessions
253 .try_insert(identifier.into_bytes(), proteus_conversation)
254 .is_err()
255 {
256 break;
257 }
258 }
259
260 Ok(proteus_sessions)
261 }
262
263 pub async fn session_from_prekey(
265 &mut self,
266 session_id: &str,
267 key: &[u8],
268 ) -> Result<GroupStoreValue<ProteusConversationSession>> {
269 let prekey = PreKeyBundle::deserialise(key).map_err(ProteusError::wrap("deserializing prekey bundle"))?;
270 let proteus_session = Session::init_from_prekey::<core_crypto_keystore::CryptoKeystoreError>(
302 self.proteus_identity.clone(),
303 prekey,
304 )
305 .map_err(ProteusError::wrap("initializing session from prekey"))?;
306
307 let proteus_conversation = ProteusConversationSession {
308 identifier: session_id.into(),
309 session: proteus_session,
310 };
311
312 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
313
314 Ok(self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone())
315 }
316
317 pub(crate) async fn session_from_message(
319 &mut self,
320 keystore: &mut CryptoKeystore,
321 session_id: &str,
322 envelope: &[u8],
323 ) -> Result<(GroupStoreValue<ProteusConversationSession>, Vec<u8>)> {
324 let message = Envelope::deserialise(envelope).map_err(ProteusError::wrap("deserialising envelope"))?;
325 let (session, payload) = Session::init_from_message(self.proteus_identity.clone(), keystore, &message)
326 .await
327 .map_err(ProteusError::wrap("initializing session from message"))?;
328
329 let proteus_conversation = ProteusConversationSession {
330 identifier: session_id.into(),
331 session,
332 };
333
334 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
335
336 Ok((
337 self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone(),
338 payload,
339 ))
340 }
341
342 pub(crate) async fn session_save(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
346 if let Some(session) = self
347 .proteus_sessions
348 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
349 .await?
350 {
351 Self::session_save_by_ref(keystore, session).await?;
352 }
353
354 Ok(())
355 }
356
357 pub(crate) async fn session_save_by_ref(
358 keystore: &CryptoKeystore,
359 session: GroupStoreValue<ProteusConversationSession>,
360 ) -> Result<()> {
361 let session = session.read().await;
362 let db_session = ProteusSession {
363 id: session.identifier().to_string(),
364 session: session
365 .session
366 .serialise()
367 .map_err(ProteusError::wrap("serializing session"))?,
368 };
369 keystore
370 .save(db_session)
371 .await
372 .map_err(KeystoreError::wrap("saving proteus session"))?;
373 Ok(())
374 }
375
376 pub(crate) async fn session_delete(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
378 if keystore.remove::<ProteusSession, _>(session_id).await.is_ok() {
379 let _ = self.proteus_sessions.remove(session_id.as_bytes());
380 }
381 Ok(())
382 }
383
384 pub(crate) async fn session(
386 &mut self,
387 session_id: &str,
388 keystore: &CryptoKeystore,
389 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
390 self.proteus_sessions
391 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
392 .await
393 }
394
395 pub(crate) async fn session_exists(&mut self, session_id: &str, keystore: &CryptoKeystore) -> bool {
397 self.session(session_id, keystore).await.ok().flatten().is_some()
398 }
399
400 pub(crate) async fn decrypt(
403 &mut self,
404 keystore: &mut CryptoKeystore,
405 session_id: &str,
406 ciphertext: &[u8],
407 ) -> Result<Vec<u8>> {
408 let session = self
409 .proteus_sessions
410 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
411 .await?
412 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
413 .map_err(ProteusError::wrap("getting session"))?;
414
415 let plaintext = session.write().await.decrypt(keystore, ciphertext).await?;
416 ProteusCentral::session_save_by_ref(keystore, session).await?;
417
418 Ok(plaintext)
419 }
420
421 pub(crate) async fn encrypt(
423 &mut self,
424 keystore: &mut CryptoKeystore,
425 session_id: &str,
426 plaintext: &[u8],
427 ) -> Result<Vec<u8>> {
428 let session = self
429 .session(session_id, keystore)
430 .await?
431 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
432 .map_err(ProteusError::wrap("getting session"))?;
433
434 let ciphertext = session.write().await.encrypt(plaintext)?;
435 ProteusCentral::session_save_by_ref(keystore, session).await?;
436
437 Ok(ciphertext)
438 }
439
440 pub(crate) async fn encrypt_batched(
443 &mut self,
444 keystore: &mut CryptoKeystore,
445 sessions: &[impl AsRef<str>],
446 plaintext: &[u8],
447 ) -> Result<HashMap<String, Vec<u8>>> {
448 let mut acc = HashMap::new();
449 for session_id in sessions {
450 if let Some(session) = self.session(session_id.as_ref(), keystore).await? {
451 let mut session_w = session.write().await;
452 acc.insert(session_w.identifier.clone(), session_w.encrypt(plaintext)?);
453 drop(session_w);
454
455 ProteusCentral::session_save_by_ref(keystore, session).await?;
456 }
457 }
458 Ok(acc)
459 }
460
461 pub(crate) async fn new_prekey(&self, id: u16, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
463 use proteus_wasm::keys::{PreKey, PreKeyId};
464
465 let prekey_id = PreKeyId::new(id);
466 let prekey = PreKey::new(prekey_id);
467 let keystore_prekey = core_crypto_keystore::entities::ProteusPrekey::from_raw(
468 id,
469 prekey.serialise().map_err(ProteusError::wrap("serialising prekey"))?,
470 );
471 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &prekey);
472 let bundle = bundle
473 .serialise()
474 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
475 keystore
476 .save(keystore_prekey)
477 .await
478 .map_err(KeystoreError::wrap("saving keystore prekey"))?;
479 Ok(bundle)
480 }
481
482 pub(crate) async fn new_prekey_auto(&self, keystore: &CryptoKeystore) -> Result<(u16, Vec<u8>)> {
486 let id = core_crypto_keystore::entities::ProteusPrekey::get_free_id(keystore)
487 .await
488 .map_err(KeystoreError::wrap("getting proteus prekey by id"))?;
489 Ok((id, self.new_prekey(id, keystore).await?))
490 }
491
492 pub fn last_resort_prekey_id() -> u16 {
494 proteus_wasm::keys::MAX_PREKEY_ID.value()
495 }
496
497 pub(crate) async fn last_resort_prekey(&self, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
500 let last_resort = if let Some(last_resort) = keystore
501 .find::<core_crypto_keystore::entities::ProteusPrekey>(
502 Self::last_resort_prekey_id().to_le_bytes().as_slice(),
503 )
504 .await
505 .map_err(KeystoreError::wrap("finding proteus prekey"))?
506 {
507 proteus_wasm::keys::PreKey::deserialise(&last_resort.prekey)
508 .map_err(ProteusError::wrap("deserialising proteus prekey"))?
509 } else {
510 let last_resort = proteus_wasm::keys::PreKey::last_resort();
511
512 use core_crypto_keystore::CryptoKeystoreProteus as _;
513 keystore
514 .proteus_store_prekey(
515 Self::last_resort_prekey_id(),
516 &last_resort
517 .serialise()
518 .map_err(ProteusError::wrap("serialising last resort prekey"))?,
519 )
520 .await
521 .map_err(KeystoreError::wrap("storing proteus prekey"))?;
522
523 last_resort
524 };
525
526 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &last_resort);
527 let bundle = bundle
528 .serialise()
529 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
530
531 Ok(bundle)
532 }
533
534 pub fn identity(&self) -> &IdentityKeyPair {
536 self.proteus_identity.as_ref()
537 }
538
539 pub fn fingerprint(&self) -> String {
541 self.proteus_identity.as_ref().public_key.fingerprint()
542 }
543
544 pub(crate) async fn fingerprint_local(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
549 let session = self
550 .session(session_id, keystore)
551 .await?
552 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
553 .map_err(ProteusError::wrap("getting session"))?;
554 let fingerprint = session.read().await.fingerprint_local();
555 Ok(fingerprint)
556 }
557
558 pub(crate) async fn fingerprint_remote(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
563 let session = self
564 .session(session_id, keystore)
565 .await?
566 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
567 .map_err(ProteusError::wrap("getting session"))?;
568 let fingerprint = session.read().await.fingerprint_remote();
569 Ok(fingerprint)
570 }
571
572 pub fn fingerprint_prekeybundle(prekey: &[u8]) -> Result<String> {
577 let prekey = PreKeyBundle::deserialise(prekey).map_err(ProteusError::wrap("deserialising prekey bundle"))?;
578 Ok(prekey.identity_key.fingerprint())
579 }
580}
581
582#[cfg(test)]
583mod tests {
584 use crate::{
585 prelude::{CertificateBundle, ClientIdentifier, MlsCredentialType, Session, SessionConfig},
586 test_utils::{proteus_utils::*, x509::X509TestChain, *},
587 };
588
589 use crate::prelude::INITIAL_KEYING_MATERIAL_COUNT;
590
591 use super::*;
592
593 use core_crypto_keystore::{ConnectionType, DatabaseKey};
594
595 #[apply(all_cred_cipher)]
596 async fn cc_can_init(case: TestContext) {
597 #[cfg(not(target_family = "wasm"))]
598 let (path, db_file) = tmp_db_file();
599 #[cfg(target_family = "wasm")]
600 let (path, _) = tmp_db_file();
601 let client_id = "alice".into();
602 let cfg = SessionConfig::builder()
603 .persistent(&path)
604 .database_key(DatabaseKey::generate())
605 .client_id(client_id)
606 .ciphersuites([case.ciphersuite()])
607 .build()
608 .validate()
609 .unwrap();
610
611 let cc: CoreCrypto = Session::try_new(cfg).await.unwrap().into();
612 let context = cc.new_transaction().await.unwrap();
613 assert!(context.proteus_init().await.is_ok());
614 assert!(context.proteus_new_prekey(1).await.is_ok());
615 context.finish().await.unwrap();
616 #[cfg(not(target_family = "wasm"))]
617 drop(db_file);
618 }
619
620 #[apply(all_cred_cipher)]
621 async fn cc_can_2_phase_init(case: TestContext) {
622 #[cfg(not(target_family = "wasm"))]
623 let (path, db_file) = tmp_db_file();
624 #[cfg(target_family = "wasm")]
625 let (path, _) = tmp_db_file();
626 let cfg = SessionConfig::builder()
628 .persistent(&path)
629 .database_key(DatabaseKey::generate())
630 .ciphersuites([case.ciphersuite()])
631 .build()
632 .validate()
633 .unwrap();
634
635 let cc: CoreCrypto = Session::try_new(cfg).await.unwrap().into();
636 let transaction = cc.new_transaction().await.unwrap();
637 let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
638 x509_test_chain.register_with_central(&transaction).await;
639 assert!(transaction.proteus_init().await.is_ok());
640 assert!(transaction.proteus_new_prekey(1).await.is_ok());
642 let client_id = "alice";
644 let identifier = match case.credential_type {
645 MlsCredentialType::Basic => ClientIdentifier::Basic(client_id.into()),
646 MlsCredentialType::X509 => {
647 CertificateBundle::rand_identifier(client_id, &[x509_test_chain.find_local_intermediate_ca()])
648 }
649 };
650 transaction
651 .mls_init(
652 identifier,
653 vec![case.ciphersuite()],
654 Some(INITIAL_KEYING_MATERIAL_COUNT),
655 )
656 .await
657 .unwrap();
658 assert_eq!(
660 transaction
661 .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, 2)
662 .await
663 .unwrap()
664 .len(),
665 2
666 );
667 #[cfg(not(target_family = "wasm"))]
668 drop(db_file);
669 }
670
671 #[macro_rules_attribute::apply(smol_macros::test)]
672 async fn can_init() {
673 #[cfg(not(target_family = "wasm"))]
674 let (path, db_file) = tmp_db_file();
675 #[cfg(target_family = "wasm")]
676 let (path, _) = tmp_db_file();
677 let key = DatabaseKey::generate();
678 let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
679 .await
680 .unwrap();
681 keystore.new_transaction().await.unwrap();
682 let central = ProteusCentral::try_new(&keystore).await.unwrap();
683 let identity = (*central.proteus_identity).clone();
684 keystore.commit_transaction().await.unwrap();
685
686 let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
687 .await
688 .unwrap();
689 keystore.new_transaction().await.unwrap();
690 let central = ProteusCentral::try_new(&keystore).await.unwrap();
691 keystore.commit_transaction().await.unwrap();
692 assert_eq!(identity, *central.proteus_identity);
693
694 keystore.wipe().await.unwrap();
695 #[cfg(not(target_family = "wasm"))]
696 drop(db_file);
697 }
698
699 #[macro_rules_attribute::apply(smol_macros::test)]
700 async fn can_talk_with_proteus() {
701 #[cfg(not(target_family = "wasm"))]
702 let (path, db_file) = tmp_db_file();
703 #[cfg(target_family = "wasm")]
704 let (path, _) = tmp_db_file();
705
706 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
707
708 let key = DatabaseKey::generate();
709 let mut keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
710 .await
711 .unwrap();
712 keystore.new_transaction().await.unwrap();
713
714 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
715
716 let mut bob = CryptoboxLike::init();
717 let bob_pk_bundle = bob.new_prekey();
718
719 alice
720 .session_from_prekey(&session_id, &bob_pk_bundle.serialise().unwrap())
721 .await
722 .unwrap();
723
724 let message = b"Hello world";
725
726 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
727 let decrypted = bob.decrypt(&session_id, &encrypted).await;
728 assert_eq!(decrypted, message);
729
730 let encrypted = bob.encrypt(&session_id, message);
731 let decrypted = alice.decrypt(&mut keystore, &session_id, &encrypted).await.unwrap();
732 assert_eq!(decrypted, message);
733
734 keystore.commit_transaction().await.unwrap();
735 keystore.wipe().await.unwrap();
736 #[cfg(not(target_family = "wasm"))]
737 drop(db_file);
738 }
739
740 #[macro_rules_attribute::apply(smol_macros::test)]
741 async fn can_produce_proteus_consumed_prekeys() {
742 #[cfg(not(target_family = "wasm"))]
743 let (path, db_file) = tmp_db_file();
744 #[cfg(target_family = "wasm")]
745 let (path, _) = tmp_db_file();
746
747 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
748
749 let key = DatabaseKey::generate();
750 let mut keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
751 .await
752 .unwrap();
753 keystore.new_transaction().await.unwrap();
754 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
755
756 let mut bob = CryptoboxLike::init();
757
758 let alice_prekey_bundle_ser = alice.new_prekey(1, &keystore).await.unwrap();
759
760 bob.init_session_from_prekey_bundle(&session_id, &alice_prekey_bundle_ser);
761 let message = b"Hello world!";
762 let encrypted = bob.encrypt(&session_id, message);
763
764 let (_, decrypted) = alice
765 .session_from_message(&mut keystore, &session_id, &encrypted)
766 .await
767 .unwrap();
768
769 assert_eq!(message, decrypted.as_slice());
770
771 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
772 let decrypted = bob.decrypt(&session_id, &encrypted).await;
773
774 assert_eq!(message, decrypted.as_slice());
775 keystore.commit_transaction().await.unwrap();
776 keystore.wipe().await.unwrap();
777 #[cfg(not(target_family = "wasm"))]
778 drop(db_file);
779 }
780
781 #[macro_rules_attribute::apply(smol_macros::test)]
782 async fn auto_prekeys_are_sequential() {
783 use core_crypto_keystore::entities::ProteusPrekey;
784 const GAP_AMOUNT: u16 = 5;
785 const ID_TEST_RANGE: std::ops::RangeInclusive<u16> = 1..=30;
786
787 #[cfg(not(target_family = "wasm"))]
788 let (path, db_file) = tmp_db_file();
789 #[cfg(target_family = "wasm")]
790 let (path, _) = tmp_db_file();
791
792 let key = DatabaseKey::generate();
793 let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
794 .await
795 .unwrap();
796 keystore.new_transaction().await.unwrap();
797 let alice = ProteusCentral::try_new(&keystore).await.unwrap();
798
799 for i in ID_TEST_RANGE {
800 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
801 assert_eq!(i, pk_id);
802 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
803 assert_eq!(prekey.prekey_id.value(), pk_id);
804 }
805
806 use rand::Rng as _;
807 let mut rng = rand::thread_rng();
808 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
809 gap_ids.sort();
810 gap_ids.dedup();
811 while gap_ids.len() < GAP_AMOUNT as usize {
812 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
813 gap_ids.sort();
814 gap_ids.dedup();
815 }
816 for gap_id in gap_ids.iter() {
817 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
818 }
819
820 gap_ids.sort();
821
822 for gap_id in gap_ids.iter() {
823 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
824 assert_eq!(pk_id, *gap_id);
825 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
826 assert_eq!(prekey.prekey_id.value(), *gap_id);
827 }
828
829 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
830 gap_ids.sort();
831 gap_ids.dedup();
832 while gap_ids.len() < GAP_AMOUNT as usize {
833 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
834 gap_ids.sort();
835 gap_ids.dedup();
836 }
837 for gap_id in gap_ids.iter() {
838 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
839 }
840
841 let potential_range = *ID_TEST_RANGE.end()..=(*ID_TEST_RANGE.end() * 2);
842 let potential_range_check = potential_range.clone();
843 for _ in potential_range {
844 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
845 assert!(gap_ids.contains(&pk_id) || potential_range_check.contains(&pk_id));
846 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
847 assert_eq!(prekey.prekey_id.value(), pk_id);
848 }
849 keystore.commit_transaction().await.unwrap();
850 keystore.wipe().await.unwrap();
851 #[cfg(not(target_family = "wasm"))]
852 drop(db_file);
853 }
854}