1use crate::{
18 CoreCrypto, Error, KeystoreError, LeafError, ProteusError, Result,
19 context::CentralContext,
20 group_store::{GroupStore, GroupStoreValue},
21};
22use core_crypto_keystore::{
23 Connection as CryptoKeystore,
24 connection::FetchFromDatabase,
25 entities::{ProteusIdentity, ProteusSession},
26};
27use proteus_wasm::{
28 keys::{IdentityKeyPair, PreKeyBundle},
29 message::Envelope,
30 session::Session,
31};
32use std::{collections::HashMap, sync::Arc};
33
34pub type SessionIdentifier = String;
36
37#[derive(Debug)]
39pub struct ProteusConversationSession {
40 pub(crate) identifier: SessionIdentifier,
41 pub(crate) session: Session<Arc<IdentityKeyPair>>,
42}
43
44impl ProteusConversationSession {
45 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
47 self.session
48 .encrypt(plaintext)
49 .and_then(|e| e.serialise())
50 .map_err(ProteusError::wrap("encrypting message for proteus session"))
51 .map_err(Into::into)
52 }
53
54 pub async fn decrypt(
56 &mut self,
57 store: &mut core_crypto_keystore::Connection,
58 ciphertext: &[u8],
59 ) -> Result<Vec<u8>> {
60 let envelope = Envelope::deserialise(ciphertext).map_err(ProteusError::wrap("deserializing envelope"))?;
61 self.session
62 .decrypt(store, &envelope)
63 .await
64 .map_err(ProteusError::wrap("decrypting message for proteus session"))
65 .map_err(Into::into)
66 }
67
68 pub fn identifier(&self) -> &str {
70 &self.identifier
71 }
72
73 pub fn fingerprint_local(&self) -> String {
75 self.session.local_identity().fingerprint()
76 }
77
78 pub fn fingerprint_remote(&self) -> String {
80 self.session.remote_identity().fingerprint()
81 }
82}
83
84impl CoreCrypto {
85 pub async fn proteus_session(
89 &self,
90 session_id: &str,
91 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
92 let mut mutex = self.proteus.lock().await;
93 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
94 let keystore = self.mls.mls_backend.keystore();
95 proteus.session(session_id, &keystore).await
96 }
97
98 pub async fn proteus_session_exists(&self, session_id: &str) -> Result<bool> {
102 let mut mutex = self.proteus.lock().await;
103 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
104 let keystore = self.mls.mls_backend.keystore();
105 Ok(proteus.session_exists(session_id, &keystore).await)
106 }
107
108 pub fn proteus_last_resort_prekey_id() -> u16 {
110 ProteusCentral::last_resort_prekey_id()
111 }
112
113 pub async fn proteus_fingerprint(&self) -> Result<String> {
117 let mutex = self.proteus.lock().await;
118 let proteus = mutex.as_ref().ok_or(Error::ProteusNotInitialized)?;
119 Ok(proteus.fingerprint())
120 }
121
122 pub async fn proteus_fingerprint_local(&self, session_id: &str) -> Result<String> {
126 let mut mutex = self.proteus.lock().await;
127 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
128 let keystore = self.mls.mls_backend.keystore();
129 proteus.fingerprint_local(session_id, &keystore).await
130 }
131
132 pub async fn proteus_fingerprint_remote(&self, session_id: &str) -> Result<String> {
136 let mut mutex = self.proteus.lock().await;
137 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
138 let keystore = self.mls.mls_backend.keystore();
139 proteus.fingerprint_remote(session_id, &keystore).await
140 }
141}
142
143impl CentralContext {
144 pub async fn proteus_init(&self) -> Result<()> {
146 let keystore = self.keystore().await?;
147 let proteus_client = ProteusCentral::try_new(&keystore).await?;
148
149 let _ = proteus_client.last_resort_prekey(&keystore).await?;
151
152 let mutex = self.proteus_central().await?;
153 let mut guard = mutex.lock().await;
154 *guard = Some(proteus_client);
155 Ok(())
156 }
157
158 pub async fn proteus_reload_sessions(&self) -> Result<()> {
162 let arc = self.proteus_central().await?;
163 let mut mutex = arc.lock().await;
164 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
165 let keystore = self.keystore().await?;
166 proteus.reload_sessions(&keystore).await
167 }
168
169 pub async fn proteus_session_from_prekey(
173 &self,
174 session_id: &str,
175 prekey: &[u8],
176 ) -> Result<GroupStoreValue<ProteusConversationSession>> {
177 let arc = self.proteus_central().await?;
178 let mut mutex = arc.lock().await;
179 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
180 let keystore = self.keystore().await?;
181 let session = proteus.session_from_prekey(session_id, prekey).await?;
182 ProteusCentral::session_save_by_ref(&keystore, session.clone()).await?;
183
184 Ok(session)
185 }
186
187 pub async fn proteus_session_from_message(
191 &self,
192 session_id: &str,
193 envelope: &[u8],
194 ) -> Result<(GroupStoreValue<ProteusConversationSession>, Vec<u8>)> {
195 let arc = self.proteus_central().await?;
196 let mut mutex = arc.lock().await;
197 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
198 let mut keystore = self.keystore().await?;
199 let (session, message) = proteus
200 .session_from_message(&mut keystore, session_id, envelope)
201 .await?;
202 ProteusCentral::session_save_by_ref(&keystore, session.clone()).await?;
203
204 Ok((session, message))
205 }
206
207 pub async fn proteus_session_save(&self, session_id: &str) -> Result<()> {
211 let arc = self.proteus_central().await?;
212 let mut mutex = arc.lock().await;
213 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
214 let keystore = self.keystore().await?;
215 proteus.session_save(&keystore, session_id).await
216 }
217
218 pub async fn proteus_session_delete(&self, session_id: &str) -> Result<()> {
222 let arc = self.proteus_central().await?;
223 let mut mutex = arc.lock().await;
224 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
225 let keystore = self.keystore().await?;
226 proteus.session_delete(&keystore, session_id).await
227 }
228
229 pub async fn proteus_session(
233 &self,
234 session_id: &str,
235 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
236 let arc = self.proteus_central().await?;
237 let mut mutex = arc.lock().await;
238 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
239 let keystore = self.keystore().await?;
240 proteus.session(session_id, &keystore).await
241 }
242
243 pub async fn proteus_session_exists(&self, session_id: &str) -> Result<bool> {
247 let arc = self.proteus_central().await?;
248 let mut mutex = arc.lock().await;
249 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
250 let keystore = self.keystore().await?;
251 Ok(proteus.session_exists(session_id, &keystore).await)
252 }
253
254 pub async fn proteus_decrypt(&self, session_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
258 let arc = self.proteus_central().await?;
259 let mut mutex = arc.lock().await;
260 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
261 let mut keystore = self.keystore().await?;
262 proteus.decrypt(&mut keystore, session_id, ciphertext).await
263 }
264
265 pub async fn proteus_encrypt(&self, session_id: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
269 let arc = self.proteus_central().await?;
270 let mut mutex = arc.lock().await;
271 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
272 let mut keystore = self.keystore().await?;
273 proteus.encrypt(&mut keystore, session_id, plaintext).await
274 }
275
276 pub async fn proteus_encrypt_batched(
281 &self,
282 sessions: &[impl AsRef<str>],
283 plaintext: &[u8],
284 ) -> Result<std::collections::HashMap<String, Vec<u8>>> {
285 let arc = self.proteus_central().await?;
286 let mut mutex = arc.lock().await;
287 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
288 let mut keystore = self.keystore().await?;
289 proteus.encrypt_batched(&mut keystore, sessions, plaintext).await
290 }
291
292 pub async fn proteus_new_prekey(&self, prekey_id: u16) -> Result<Vec<u8>> {
296 let arc = self.proteus_central().await?;
297 let mut mutex = arc.lock().await;
298 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
299 let keystore = self.keystore().await?;
300 proteus.new_prekey(prekey_id, &keystore).await
301 }
302
303 pub async fn proteus_new_prekey_auto(&self) -> Result<(u16, Vec<u8>)> {
307 let arc = self.proteus_central().await?;
308 let mut mutex = arc.lock().await;
309 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
310 let keystore = self.keystore().await?;
311 proteus.new_prekey_auto(&keystore).await
312 }
313
314 pub async fn proteus_last_resort_prekey(&self) -> Result<Vec<u8>> {
316 let arc = self.proteus_central().await?;
317 let mut mutex = arc.lock().await;
318 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
319 let keystore = self.keystore().await?;
320
321 proteus.last_resort_prekey(&keystore).await
322 }
323
324 pub fn proteus_last_resort_prekey_id() -> u16 {
326 ProteusCentral::last_resort_prekey_id()
327 }
328
329 pub async fn proteus_fingerprint(&self) -> Result<String> {
333 let arc = self.proteus_central().await?;
334 let mut mutex = arc.lock().await;
335 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
336 Ok(proteus.fingerprint())
337 }
338
339 pub async fn proteus_fingerprint_local(&self, session_id: &str) -> Result<String> {
343 let arc = self.proteus_central().await?;
344 let mut mutex = arc.lock().await;
345 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
346 let keystore = self.keystore().await?;
347 proteus.fingerprint_local(session_id, &keystore).await
348 }
349
350 pub async fn proteus_fingerprint_remote(&self, session_id: &str) -> Result<String> {
354 let arc = self.proteus_central().await?;
355 let mut mutex = arc.lock().await;
356 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
357 let keystore = self.keystore().await?;
358 proteus.fingerprint_remote(session_id, &keystore).await
359 }
360
361 pub async fn proteus_cryptobox_migrate(&self, path: &str) -> Result<()> {
365 let keystore = self.keystore().await?;
366 ProteusCentral::cryptobox_migrate(&keystore, path).await
367 }
368}
369
370#[derive(Debug)]
375pub struct ProteusCentral {
376 proteus_identity: Arc<IdentityKeyPair>,
377 proteus_sessions: GroupStore<ProteusConversationSession>,
378}
379
380impl ProteusCentral {
381 pub async fn try_new(keystore: &CryptoKeystore) -> Result<Self> {
383 let proteus_identity: Arc<IdentityKeyPair> = Arc::new(Self::load_or_create_identity(keystore).await?);
384 let proteus_sessions = Self::restore_sessions(keystore, &proteus_identity).await?;
385
386 Ok(Self {
387 proteus_identity,
388 proteus_sessions,
389 })
390 }
391
392 pub(crate) async fn reload_sessions(&mut self, keystore: &CryptoKeystore) -> Result<()> {
394 self.proteus_sessions = Self::restore_sessions(keystore, &self.proteus_identity).await?;
395 Ok(())
396 }
397
398 async fn load_or_create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
401 let Some(identity) = keystore
402 .find::<ProteusIdentity>(&[])
403 .await
404 .map_err(KeystoreError::wrap("finding proteus identity"))?
405 else {
406 return Self::create_identity(keystore).await;
407 };
408
409 let sk = identity.sk_raw();
410 let pk = identity.pk_raw();
411
412 IdentityKeyPair::from_raw_key_pair(*sk, *pk)
414 .map_err(ProteusError::wrap("constructing identity keypair"))
415 .map_err(Into::into)
416 }
417
418 async fn create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
420 let kp = IdentityKeyPair::new();
421 let pk = kp.public_key.public_key.as_slice().to_vec();
422
423 let ks_identity = ProteusIdentity {
424 sk: kp.secret_key.to_keypair_bytes().into(),
425 pk,
426 };
427 keystore
428 .save(ks_identity)
429 .await
430 .map_err(KeystoreError::wrap("saving new proteus identity"))?;
431
432 Ok(kp)
433 }
434
435 async fn restore_sessions(
437 keystore: &core_crypto_keystore::Connection,
438 identity: &Arc<IdentityKeyPair>,
439 ) -> Result<GroupStore<ProteusConversationSession>> {
440 let mut proteus_sessions = GroupStore::new_with_limit(crate::group_store::ITEM_LIMIT * 2);
441 for session in keystore
442 .find_all::<ProteusSession>(Default::default())
443 .await
444 .map_err(KeystoreError::wrap("finding all proteus sessions"))?
445 .into_iter()
446 {
447 let proteus_session = Session::deserialise(identity.clone(), &session.session)
448 .map_err(ProteusError::wrap("deserializing session"))?;
449
450 let identifier = session.id.clone();
451
452 let proteus_conversation = ProteusConversationSession {
453 identifier: identifier.clone(),
454 session: proteus_session,
455 };
456
457 if proteus_sessions
458 .try_insert(identifier.into_bytes(), proteus_conversation)
459 .is_err()
460 {
461 break;
462 }
463 }
464
465 Ok(proteus_sessions)
466 }
467
468 pub async fn session_from_prekey(
470 &mut self,
471 session_id: &str,
472 key: &[u8],
473 ) -> Result<GroupStoreValue<ProteusConversationSession>> {
474 let prekey = PreKeyBundle::deserialise(key).map_err(ProteusError::wrap("deserializing prekey bundle"))?;
475 let proteus_session = Session::init_from_prekey::<core_crypto_keystore::CryptoKeystoreError>(
507 self.proteus_identity.clone(),
508 prekey,
509 )
510 .map_err(ProteusError::wrap("initializing session from prekey"))?;
511
512 let proteus_conversation = ProteusConversationSession {
513 identifier: session_id.into(),
514 session: proteus_session,
515 };
516
517 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
518
519 Ok(self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone())
520 }
521
522 pub(crate) async fn session_from_message(
524 &mut self,
525 keystore: &mut CryptoKeystore,
526 session_id: &str,
527 envelope: &[u8],
528 ) -> Result<(GroupStoreValue<ProteusConversationSession>, Vec<u8>)> {
529 let message = Envelope::deserialise(envelope).map_err(ProteusError::wrap("deserialising envelope"))?;
530 let (session, payload) = Session::init_from_message(self.proteus_identity.clone(), keystore, &message)
531 .await
532 .map_err(ProteusError::wrap("initializing session from message"))?;
533
534 let proteus_conversation = ProteusConversationSession {
535 identifier: session_id.into(),
536 session,
537 };
538
539 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
540
541 Ok((
542 self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone(),
543 payload,
544 ))
545 }
546
547 pub(crate) async fn session_save(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
551 if let Some(session) = self
552 .proteus_sessions
553 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
554 .await?
555 {
556 Self::session_save_by_ref(keystore, session).await?;
557 }
558
559 Ok(())
560 }
561
562 async fn session_save_by_ref(
563 keystore: &CryptoKeystore,
564 session: GroupStoreValue<ProteusConversationSession>,
565 ) -> Result<()> {
566 let session = session.read().await;
567 let db_session = ProteusSession {
568 id: session.identifier().to_string(),
569 session: session
570 .session
571 .serialise()
572 .map_err(ProteusError::wrap("serializing session"))?,
573 };
574 keystore
575 .save(db_session)
576 .await
577 .map_err(KeystoreError::wrap("saving proteus session"))?;
578 Ok(())
579 }
580
581 pub(crate) async fn session_delete(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
583 if keystore.remove::<ProteusSession, _>(session_id).await.is_ok() {
584 let _ = self.proteus_sessions.remove(session_id.as_bytes());
585 }
586 Ok(())
587 }
588
589 pub(crate) async fn session(
591 &mut self,
592 session_id: &str,
593 keystore: &CryptoKeystore,
594 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
595 self.proteus_sessions
596 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
597 .await
598 }
599
600 pub(crate) async fn session_exists(&mut self, session_id: &str, keystore: &CryptoKeystore) -> bool {
602 self.session(session_id, keystore).await.ok().flatten().is_some()
603 }
604
605 pub(crate) async fn decrypt(
608 &mut self,
609 keystore: &mut CryptoKeystore,
610 session_id: &str,
611 ciphertext: &[u8],
612 ) -> Result<Vec<u8>> {
613 let session = self
614 .proteus_sessions
615 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
616 .await?
617 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
618 .map_err(ProteusError::wrap("getting session"))?;
619
620 let plaintext = session.write().await.decrypt(keystore, ciphertext).await?;
621 ProteusCentral::session_save_by_ref(keystore, session).await?;
622
623 Ok(plaintext)
624 }
625
626 pub(crate) async fn encrypt(
628 &mut self,
629 keystore: &mut CryptoKeystore,
630 session_id: &str,
631 plaintext: &[u8],
632 ) -> Result<Vec<u8>> {
633 let session = self
634 .session(session_id, keystore)
635 .await?
636 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
637 .map_err(ProteusError::wrap("getting session"))?;
638
639 let ciphertext = session.write().await.encrypt(plaintext)?;
640 ProteusCentral::session_save_by_ref(keystore, session).await?;
641
642 Ok(ciphertext)
643 }
644
645 pub(crate) async fn encrypt_batched(
648 &mut self,
649 keystore: &mut CryptoKeystore,
650 sessions: &[impl AsRef<str>],
651 plaintext: &[u8],
652 ) -> Result<HashMap<String, Vec<u8>>> {
653 let mut acc = HashMap::new();
654 for session_id in sessions {
655 if let Some(session) = self.session(session_id.as_ref(), keystore).await? {
656 let mut session_w = session.write().await;
657 acc.insert(session_w.identifier.clone(), session_w.encrypt(plaintext)?);
658 drop(session_w);
659
660 ProteusCentral::session_save_by_ref(keystore, session).await?;
661 }
662 }
663 Ok(acc)
664 }
665
666 pub(crate) async fn new_prekey(&self, id: u16, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
668 use proteus_wasm::keys::{PreKey, PreKeyId};
669
670 let prekey_id = PreKeyId::new(id);
671 let prekey = PreKey::new(prekey_id);
672 let keystore_prekey = core_crypto_keystore::entities::ProteusPrekey::from_raw(
673 id,
674 prekey.serialise().map_err(ProteusError::wrap("serialising prekey"))?,
675 );
676 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &prekey);
677 let bundle = bundle
678 .serialise()
679 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
680 keystore
681 .save(keystore_prekey)
682 .await
683 .map_err(KeystoreError::wrap("saving keystore prekey"))?;
684 Ok(bundle)
685 }
686
687 pub(crate) async fn new_prekey_auto(&self, keystore: &CryptoKeystore) -> Result<(u16, Vec<u8>)> {
691 let id = core_crypto_keystore::entities::ProteusPrekey::get_free_id(keystore)
692 .await
693 .map_err(KeystoreError::wrap("getting proteus prekey by id"))?;
694 Ok((id, self.new_prekey(id, keystore).await?))
695 }
696
697 pub fn last_resort_prekey_id() -> u16 {
699 proteus_wasm::keys::MAX_PREKEY_ID.value()
700 }
701
702 pub(crate) async fn last_resort_prekey(&self, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
705 let last_resort = if let Some(last_resort) = keystore
706 .find::<core_crypto_keystore::entities::ProteusPrekey>(
707 Self::last_resort_prekey_id().to_le_bytes().as_slice(),
708 )
709 .await
710 .map_err(KeystoreError::wrap("finding proteus prekey"))?
711 {
712 proteus_wasm::keys::PreKey::deserialise(&last_resort.prekey)
713 .map_err(ProteusError::wrap("deserialising proteus prekey"))?
714 } else {
715 let last_resort = proteus_wasm::keys::PreKey::last_resort();
716
717 use core_crypto_keystore::CryptoKeystoreProteus as _;
718 keystore
719 .proteus_store_prekey(
720 Self::last_resort_prekey_id(),
721 &last_resort
722 .serialise()
723 .map_err(ProteusError::wrap("serialising last resort prekey"))?,
724 )
725 .await
726 .map_err(KeystoreError::wrap("storing proteus prekey"))?;
727
728 last_resort
729 };
730
731 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &last_resort);
732 let bundle = bundle
733 .serialise()
734 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
735
736 Ok(bundle)
737 }
738
739 pub fn identity(&self) -> &IdentityKeyPair {
741 self.proteus_identity.as_ref()
742 }
743
744 pub fn fingerprint(&self) -> String {
746 self.proteus_identity.as_ref().public_key.fingerprint()
747 }
748
749 pub(crate) async fn fingerprint_local(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
754 let session = self
755 .session(session_id, keystore)
756 .await?
757 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
758 .map_err(ProteusError::wrap("getting session"))?;
759 let fingerprint = session.read().await.fingerprint_local();
760 Ok(fingerprint)
761 }
762
763 pub(crate) async fn fingerprint_remote(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
768 let session = self
769 .session(session_id, keystore)
770 .await?
771 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
772 .map_err(ProteusError::wrap("getting session"))?;
773 let fingerprint = session.read().await.fingerprint_remote();
774 Ok(fingerprint)
775 }
776
777 pub fn fingerprint_prekeybundle(prekey: &[u8]) -> Result<String> {
782 let prekey = PreKeyBundle::deserialise(prekey).map_err(ProteusError::wrap("deserialising prekey bundle"))?;
783 Ok(prekey.identity_key.fingerprint())
784 }
785
786 #[cfg_attr(not(feature = "cryptobox-migrate"), allow(unused_variables))]
788 pub(crate) async fn cryptobox_migrate(keystore: &CryptoKeystore, path: &str) -> Result<()> {
789 cfg_if::cfg_if! {
790 if #[cfg(feature = "cryptobox-migrate")] {
791 Self::cryptobox_migrate_impl(keystore, path).await?;
792 Ok(())
793 } else {
794 Err(Error::FeatureDisabled("cryptobox-migrate"))
795 }
796 }
797 }
798}
799
800#[cfg(feature = "cryptobox-migrate")]
801#[allow(dead_code)]
802impl ProteusCentral {
803 #[cfg(not(target_family = "wasm"))]
804 async fn cryptobox_migrate_impl(keystore: &CryptoKeystore, path: &str) -> Result<()> {
805 let root_dir = std::path::PathBuf::from(path);
806
807 if !root_dir.exists() {
808 return Err(CryptoboxMigrationError::wrap("root dir does not exist")(
809 crate::CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(path.into()),
810 )
811 .into());
812 }
813
814 let session_dir = root_dir.join("sessions");
815 let prekey_dir = root_dir.join("prekeys");
816
817 let missing_identity = Err(CryptoboxMigrationError::wrap("taking identity keypair")(
819 crate::CryptoboxMigrationErrorKind::IdentityNotFound(path.into()),
820 )
821 .into());
822
823 let identity = if let Some(store_kp) = keystore
824 .find::<ProteusIdentity>(&[])
825 .await
826 .map_err(KeystoreError::wrap("finding proteus identity"))?
827 {
828 Box::new(
829 IdentityKeyPair::from_raw_key_pair(*store_kp.sk_raw(), *store_kp.pk_raw())
830 .map_err(ProteusError::wrap("constructing identity keypair from raw keypair"))?,
831 )
832 } else {
833 let identity_dir = root_dir.join("identities");
834
835 let identity = identity_dir.join("local");
836 let legacy_identity = identity_dir.join("local_identity");
837 let kp = if legacy_identity.exists() {
839 let kp_cbor = async_fs::read(&legacy_identity)
840 .await
841 .map_err(CryptoboxMigrationError::wrap("reading legacy identity from filesystem"))?;
842 let kp = IdentityKeyPair::deserialise(&kp_cbor)
843 .map_err(ProteusError::wrap("deserialising identity keypair"))?;
844
845 Box::new(kp)
846 } else if identity.exists() {
847 let kp_cbor = async_fs::read(&identity)
848 .await
849 .map_err(CryptoboxMigrationError::wrap("reading identity from filesystem"))?;
850 let kp = proteus_wasm::identity::Identity::deserialise(&kp_cbor)
851 .map_err(ProteusError::wrap("deserialising identity"))?;
852
853 if let proteus_wasm::identity::Identity::Sec(kp) = kp {
854 kp.into_owned()
855 } else {
856 return missing_identity;
857 }
858 } else {
859 return missing_identity;
860 };
861
862 let pk = kp.public_key.public_key.as_slice().into();
863
864 let ks_identity = ProteusIdentity {
865 sk: kp.secret_key.to_keypair_bytes().into(),
866 pk,
867 };
868
869 keystore
870 .save(ks_identity)
871 .await
872 .map_err(KeystoreError::wrap("saving proteus identity"))?;
873
874 if legacy_identity.exists() {
875 async_fs::remove_file(legacy_identity)
876 .await
877 .map_err(CryptoboxMigrationError::wrap("removing legacy identity"))?;
878 }
879
880 kp
881 };
882
883 let identity = *identity;
884
885 use futures_lite::stream::StreamExt as _;
886 let mut session_entries = async_fs::read_dir(session_dir)
888 .await
889 .map_err(CryptoboxMigrationError::wrap("reading session entries"))?;
890 while let Some(session_file) = session_entries
891 .try_next()
892 .await
893 .map_err(CryptoboxMigrationError::wrap("getting next session file"))?
894 {
895 let proteus_session_id: String = session_file.file_name().to_string_lossy().to_string();
897
898 if keystore
900 .find::<ProteusSession>(proteus_session_id.as_bytes())
901 .await
902 .map_err(KeystoreError::wrap("finding proteus session by id"))?
903 .is_some()
904 {
905 continue;
906 }
907
908 let raw_session = async_fs::read(session_file.path())
909 .await
910 .map_err(CryptoboxMigrationError::wrap("reading session file"))?;
911 let Ok(_) = Session::deserialise(&identity, &raw_session) else {
913 continue;
914 };
915
916 let keystore_session = ProteusSession {
917 id: proteus_session_id,
918 session: raw_session,
919 };
920
921 keystore
922 .save(keystore_session)
923 .await
924 .map_err(KeystoreError::wrap("saving proteus session"))?;
925 }
926
927 use core_crypto_keystore::entities::ProteusPrekey;
929
930 use crate::CryptoboxMigrationError;
931 let mut prekey_entries = async_fs::read_dir(prekey_dir)
932 .await
933 .map_err(CryptoboxMigrationError::wrap("reading prekey entries"))?;
934 while let Some(prekey_file) = prekey_entries
935 .try_next()
936 .await
937 .map_err(CryptoboxMigrationError::wrap("getting next prekey file"))?
938 {
939 let proteus_prekey_id = proteus_wasm::keys::PreKeyId::new(
941 prekey_file
942 .file_name()
943 .to_string_lossy()
944 .parse()
945 .map_err(CryptoboxMigrationError::wrap("parsing prekey file name"))?,
946 );
947
948 if keystore
950 .find::<ProteusPrekey>(&proteus_prekey_id.value().to_le_bytes())
951 .await
952 .map_err(KeystoreError::wrap("finding proteus prekey by id"))?
953 .is_some()
954 {
955 continue;
956 }
957
958 let raw_prekey = async_fs::read(prekey_file.path())
959 .await
960 .map_err(CryptoboxMigrationError::wrap("reading prekey file"))?;
961 if proteus_wasm::keys::PreKey::deserialise(&raw_prekey).is_ok() {
963 let keystore_prekey = ProteusPrekey::from_raw(proteus_prekey_id.value(), raw_prekey);
964 keystore
965 .save(keystore_prekey)
966 .await
967 .map_err(KeystoreError::wrap("saving proteus prekey"))?;
968 }
969 }
970
971 Ok(())
972 }
973
974 #[cfg(target_family = "wasm")]
975 fn get_cbor_bytes_from_map(map: serde_json::map::Map<String, serde_json::Value>) -> Result<Vec<u8>> {
976 use crate::{CryptoboxMigrationError, CryptoboxMigrationErrorKind};
977
978 let Some(js_value) = map.get("serialised") else {
979 return Err(CryptoboxMigrationError::wrap("getting serialised cbor bytes from map")(
980 CryptoboxMigrationErrorKind::MissingKeyInValue("serialised".to_string()),
981 )
982 .into());
983 };
984
985 let Some(b64_value) = js_value.as_str() else {
986 return Err(CryptoboxMigrationError::wrap("getting js value as string")(
987 CryptoboxMigrationErrorKind::WrongValueType("string".to_string()),
988 )
989 .into());
990 };
991
992 use base64::Engine as _;
993 let cbor_bytes = base64::prelude::BASE64_STANDARD
994 .decode(b64_value)
995 .map_err(CryptoboxMigrationError::wrap("decoding cbor bytes"))?;
996 Ok(cbor_bytes)
997 }
998
999 #[cfg(target_family = "wasm")]
1000 async fn cryptobox_migrate_impl(keystore: &CryptoKeystore, path: &str) -> Result<()> {
1001 use rexie::{Rexie, TransactionMode};
1002
1003 use crate::{CryptoboxMigrationError, CryptoboxMigrationErrorKind};
1004 let local_identity_key = "local_identity";
1005 let local_identity_store_name = "keys";
1006 let prekeys_store_name = "prekeys";
1007 let sessions_store_name = "sessions";
1008
1009 let db = Rexie::builder(path)
1011 .build()
1012 .await
1013 .map_err(CryptoboxMigrationError::wrap("building rexie"))?;
1014
1015 let store_names = db.store_names();
1016
1017 let expected_stores = &[prekeys_store_name, sessions_store_name, local_identity_store_name];
1018
1019 if !expected_stores
1020 .iter()
1021 .map(ToString::to_string)
1022 .all(|s| store_names.contains(&s))
1023 {
1024 return Err(CryptoboxMigrationError::wrap("checking expected stores")(
1025 CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(path.into()),
1026 )
1027 .into());
1028 }
1029
1030 let mut proteus_identity = if let Some(store_kp) = keystore
1031 .find::<ProteusIdentity>(&[])
1032 .await
1033 .map_err(KeystoreError::wrap("finding proteus identity for empty id"))?
1034 {
1035 Some(
1036 proteus_wasm::keys::IdentityKeyPair::from_raw_key_pair(*store_kp.sk_raw(), *store_kp.pk_raw())
1037 .map_err(ProteusError::wrap("constructing identity keypair from raw"))?,
1038 )
1039 } else {
1040 let transaction = db
1041 .transaction(&[local_identity_store_name], TransactionMode::ReadOnly)
1042 .map_err(CryptoboxMigrationError::wrap("initializing rexie transaction"))?;
1043
1044 let identity_store = transaction
1045 .store(local_identity_store_name)
1046 .map_err(CryptoboxMigrationError::wrap("storing local identity store name"))?;
1047
1048 if let Some(cryptobox_js_value) = identity_store
1049 .get(local_identity_key.into())
1050 .await
1051 .map_err(CryptoboxMigrationError::wrap("getting local identity key js value"))?
1052 {
1053 let js_value: serde_json::map::Map<String, serde_json::Value> =
1054 serde_wasm_bindgen::from_value(cryptobox_js_value).map_err(CryptoboxMigrationError::wrap(
1055 "getting local identity key from identity store",
1056 ))?;
1057
1058 let kp_cbor = Self::get_cbor_bytes_from_map(js_value)?;
1059
1060 let kp = proteus_wasm::keys::IdentityKeyPair::deserialise(&kp_cbor)
1061 .map_err(ProteusError::wrap("deserializing identity keypair"))?;
1062
1063 let pk = kp.public_key.public_key.as_slice().to_vec();
1064
1065 let ks_identity = ProteusIdentity {
1066 sk: kp.secret_key.to_keypair_bytes().into(),
1067 pk,
1068 };
1069 keystore
1070 .save(ks_identity)
1071 .await
1072 .map_err(KeystoreError::wrap("saving proteus identity in keystore"))?;
1073
1074 Some(kp)
1075 } else {
1076 None
1077 }
1078 };
1079
1080 let Some(proteus_identity) = proteus_identity.take() else {
1081 return Err(CryptoboxMigrationError::wrap("taking proteus identity")(
1082 CryptoboxMigrationErrorKind::IdentityNotFound(path.into()),
1083 )
1084 .into());
1085 };
1086
1087 if store_names.contains(&sessions_store_name.to_string()) {
1088 let transaction = db
1089 .transaction(&[sessions_store_name], TransactionMode::ReadOnly)
1090 .map_err(CryptoboxMigrationError::wrap("starting rexie transaction"))?;
1091
1092 let sessions_store = transaction
1093 .store(sessions_store_name)
1094 .map_err(CryptoboxMigrationError::wrap("getting sessions store"))?;
1095
1096 let sessions = sessions_store
1097 .scan(None, None, None, None)
1098 .await
1099 .map_err(CryptoboxMigrationError::wrap("scanning sessions store for sessions"))?;
1100
1101 for (session_id, session_js_value) in sessions.into_iter().map(|(k, v)| (k.as_string().unwrap(), v)) {
1102 if keystore
1104 .find::<ProteusSession>(session_id.as_bytes())
1105 .await
1106 .map_err(KeystoreError::wrap("finding proteus session by id"))?
1107 .is_some()
1108 {
1109 continue;
1110 }
1111
1112 let js_value: serde_json::map::Map<String, serde_json::Value> =
1113 serde_wasm_bindgen::from_value(session_js_value).map_err(CryptoboxMigrationError::wrap(
1114 "converting session js value to serde map",
1115 ))?;
1116
1117 let session_cbor_bytes = Self::get_cbor_bytes_from_map(js_value)?;
1118
1119 if proteus_wasm::session::Session::deserialise(&proteus_identity, &session_cbor_bytes).is_ok() {
1121 let keystore_session = ProteusSession {
1122 id: session_id,
1123 session: session_cbor_bytes,
1124 };
1125
1126 keystore
1127 .save(keystore_session)
1128 .await
1129 .map_err(KeystoreError::wrap("saving keystore session"))?;
1130 }
1131 }
1132 }
1133
1134 if store_names.contains(&prekeys_store_name.to_string()) {
1135 use core_crypto_keystore::entities::ProteusPrekey;
1136
1137 let transaction = db
1138 .transaction(&[prekeys_store_name], TransactionMode::ReadOnly)
1139 .map_err(CryptoboxMigrationError::wrap("beginning rexie transaction"))?;
1140
1141 let prekeys_store = transaction
1142 .store(prekeys_store_name)
1143 .map_err(CryptoboxMigrationError::wrap("getting prekeys store"))?;
1144
1145 let prekeys = prekeys_store
1146 .scan(None, None, None, None)
1147 .await
1148 .map_err(CryptoboxMigrationError::wrap("scanning for prekeys"))?;
1149
1150 for (prekey_id, prekey_js_value) in prekeys
1151 .into_iter()
1152 .map(|(id, prekey_js_value)| (id.as_string().unwrap(), prekey_js_value))
1153 {
1154 let prekey_id: u16 = prekey_id
1155 .parse()
1156 .map_err(CryptoboxMigrationError::wrap("parsing prekey id"))?;
1157
1158 if keystore
1160 .find::<ProteusPrekey>(&prekey_id.to_le_bytes())
1161 .await
1162 .map_err(KeystoreError::wrap(
1163 "finding proteus prekey by id to check for existence",
1164 ))?
1165 .is_some()
1166 {
1167 continue;
1168 }
1169
1170 let js_value: serde_json::map::Map<String, serde_json::Value> =
1171 serde_wasm_bindgen::from_value(prekey_js_value)
1172 .map_err(CryptoboxMigrationError::wrap("converting prekey js value to serde map"))?;
1173
1174 let raw_prekey_cbor = Self::get_cbor_bytes_from_map(js_value)?;
1175
1176 if proteus_wasm::keys::PreKey::deserialise(&raw_prekey_cbor).is_ok() {
1178 let keystore_prekey = ProteusPrekey::from_raw(prekey_id, raw_prekey_cbor);
1179 keystore
1180 .save(keystore_prekey)
1181 .await
1182 .map_err(KeystoreError::wrap("saving proteus prekey"))?;
1183 }
1184 }
1185 }
1186
1187 Ok(())
1188 }
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193 use crate::{
1194 prelude::{CertificateBundle, ClientIdentifier, MlsCentral, MlsCentralConfiguration, MlsCredentialType},
1195 test_utils::{proteus_utils::*, x509::X509TestChain, *},
1196 };
1197
1198 use crate::prelude::INITIAL_KEYING_MATERIAL_COUNT;
1199 use proteus_traits::PreKeyStore;
1200 use wasm_bindgen_test::*;
1201
1202 use super::*;
1203
1204 wasm_bindgen_test_configure!(run_in_browser);
1205
1206 #[apply(all_cred_cipher)]
1207 #[wasm_bindgen_test]
1208 async fn cc_can_init(case: TestCase) {
1209 #[cfg(not(target_family = "wasm"))]
1210 let (path, db_file) = tmp_db_file();
1211 #[cfg(target_family = "wasm")]
1212 let (path, _) = tmp_db_file();
1213 let client_id = "alice".into();
1214 let cfg = MlsCentralConfiguration::try_new(
1215 path,
1216 "test".to_string(),
1217 Some(client_id),
1218 vec![case.ciphersuite()],
1219 None,
1220 Some(INITIAL_KEYING_MATERIAL_COUNT),
1221 )
1222 .unwrap();
1223 let cc: CoreCrypto = MlsCentral::try_new(cfg).await.unwrap().into();
1224 let context = cc.new_transaction().await.unwrap();
1225 assert!(context.proteus_init().await.is_ok());
1226 assert!(context.proteus_new_prekey(1).await.is_ok());
1227 context.finish().await.unwrap();
1228 #[cfg(not(target_family = "wasm"))]
1229 drop(db_file);
1230 }
1231
1232 #[apply(all_cred_cipher)]
1233 #[wasm_bindgen_test]
1234 async fn cc_can_2_phase_init(case: TestCase) {
1235 #[cfg(not(target_family = "wasm"))]
1236 let (path, db_file) = tmp_db_file();
1237 #[cfg(target_family = "wasm")]
1238 let (path, _) = tmp_db_file();
1239 let cfg = MlsCentralConfiguration::try_new(
1241 path,
1242 "test".to_string(),
1243 None,
1244 vec![case.ciphersuite()],
1245 None,
1246 Some(INITIAL_KEYING_MATERIAL_COUNT),
1247 )
1248 .unwrap();
1249 let cc: CoreCrypto = MlsCentral::try_new(cfg).await.unwrap().into();
1250 let transaction = cc.new_transaction().await.unwrap();
1251 let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
1252 x509_test_chain.register_with_central(&transaction).await;
1253 assert!(transaction.proteus_init().await.is_ok());
1254 assert!(transaction.proteus_new_prekey(1).await.is_ok());
1256 let client_id = "alice";
1258 let identifier = match case.credential_type {
1259 MlsCredentialType::Basic => ClientIdentifier::Basic(client_id.into()),
1260 MlsCredentialType::X509 => {
1261 CertificateBundle::rand_identifier(client_id, &[x509_test_chain.find_local_intermediate_ca()])
1262 }
1263 };
1264 transaction
1265 .mls_init(
1266 identifier,
1267 vec![case.ciphersuite()],
1268 Some(INITIAL_KEYING_MATERIAL_COUNT),
1269 )
1270 .await
1271 .unwrap();
1272 assert_eq!(
1274 transaction
1275 .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, 2)
1276 .await
1277 .unwrap()
1278 .len(),
1279 2
1280 );
1281 #[cfg(not(target_family = "wasm"))]
1282 drop(db_file);
1283 }
1284
1285 #[async_std::test]
1286 #[wasm_bindgen_test]
1287 async fn can_init() {
1288 #[cfg(not(target_family = "wasm"))]
1289 let (path, db_file) = tmp_db_file();
1290 #[cfg(target_family = "wasm")]
1291 let (path, _) = tmp_db_file();
1292 let keystore = core_crypto_keystore::Connection::open_with_key(&path, "test")
1293 .await
1294 .unwrap();
1295 keystore.new_transaction().await.unwrap();
1296 let central = ProteusCentral::try_new(&keystore).await.unwrap();
1297 let identity = (*central.proteus_identity).clone();
1298 keystore.commit_transaction().await.unwrap();
1299
1300 let keystore = core_crypto_keystore::Connection::open_with_key(path, "test")
1301 .await
1302 .unwrap();
1303 keystore.new_transaction().await.unwrap();
1304 let central = ProteusCentral::try_new(&keystore).await.unwrap();
1305 keystore.commit_transaction().await.unwrap();
1306 assert_eq!(identity, *central.proteus_identity);
1307
1308 keystore.wipe().await.unwrap();
1309 #[cfg(not(target_family = "wasm"))]
1310 drop(db_file);
1311 }
1312
1313 #[async_std::test]
1314 #[wasm_bindgen_test]
1315 async fn can_talk_with_proteus() {
1316 #[cfg(not(target_family = "wasm"))]
1317 let (path, db_file) = tmp_db_file();
1318 #[cfg(target_family = "wasm")]
1319 let (path, _) = tmp_db_file();
1320
1321 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1322
1323 let mut keystore = core_crypto_keystore::Connection::open_with_key(path, "test")
1324 .await
1325 .unwrap();
1326 keystore.new_transaction().await.unwrap();
1327
1328 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
1329
1330 let mut bob = CryptoboxLike::init();
1331 let bob_pk_bundle = bob.new_prekey();
1332
1333 alice
1334 .session_from_prekey(&session_id, &bob_pk_bundle.serialise().unwrap())
1335 .await
1336 .unwrap();
1337
1338 let message = b"Hello world";
1339
1340 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
1341 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1342 assert_eq!(decrypted, message);
1343
1344 let encrypted = bob.encrypt(&session_id, message);
1345 let decrypted = alice.decrypt(&mut keystore, &session_id, &encrypted).await.unwrap();
1346 assert_eq!(decrypted, message);
1347
1348 keystore.commit_transaction().await.unwrap();
1349 keystore.wipe().await.unwrap();
1350 #[cfg(not(target_family = "wasm"))]
1351 drop(db_file);
1352 }
1353
1354 #[async_std::test]
1355 #[wasm_bindgen_test]
1356 async fn can_produce_proteus_consumed_prekeys() {
1357 #[cfg(not(target_family = "wasm"))]
1358 let (path, db_file) = tmp_db_file();
1359 #[cfg(target_family = "wasm")]
1360 let (path, _) = tmp_db_file();
1361
1362 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1363
1364 let mut keystore = core_crypto_keystore::Connection::open_with_key(path, "test")
1365 .await
1366 .unwrap();
1367 keystore.new_transaction().await.unwrap();
1368 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
1369
1370 let mut bob = CryptoboxLike::init();
1371
1372 let alice_prekey_bundle_ser = alice.new_prekey(1, &keystore).await.unwrap();
1373
1374 bob.init_session_from_prekey_bundle(&session_id, &alice_prekey_bundle_ser);
1375 let message = b"Hello world!";
1376 let encrypted = bob.encrypt(&session_id, message);
1377
1378 let (_, decrypted) = alice
1379 .session_from_message(&mut keystore, &session_id, &encrypted)
1380 .await
1381 .unwrap();
1382
1383 assert_eq!(message, decrypted.as_slice());
1384
1385 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
1386 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1387
1388 assert_eq!(message, decrypted.as_slice());
1389 keystore.commit_transaction().await.unwrap();
1390 keystore.wipe().await.unwrap();
1391 #[cfg(not(target_family = "wasm"))]
1392 drop(db_file);
1393 }
1394
1395 #[async_std::test]
1396 #[wasm_bindgen_test]
1397 async fn auto_prekeys_are_sequential() {
1398 use core_crypto_keystore::entities::ProteusPrekey;
1399 const GAP_AMOUNT: u16 = 5;
1400 const ID_TEST_RANGE: std::ops::RangeInclusive<u16> = 1..=30;
1401
1402 #[cfg(not(target_family = "wasm"))]
1403 let (path, db_file) = tmp_db_file();
1404 #[cfg(target_family = "wasm")]
1405 let (path, _) = tmp_db_file();
1406
1407 let keystore = core_crypto_keystore::Connection::open_with_key(path, "test")
1408 .await
1409 .unwrap();
1410 keystore.new_transaction().await.unwrap();
1411 let alice = ProteusCentral::try_new(&keystore).await.unwrap();
1412
1413 for i in ID_TEST_RANGE {
1414 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1415 assert_eq!(i, pk_id);
1416 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1417 assert_eq!(prekey.prekey_id.value(), pk_id);
1418 }
1419
1420 use rand::Rng as _;
1421 let mut rng = rand::thread_rng();
1422 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
1423 gap_ids.sort();
1424 gap_ids.dedup();
1425 while gap_ids.len() < GAP_AMOUNT as usize {
1426 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
1427 gap_ids.sort();
1428 gap_ids.dedup();
1429 }
1430 for gap_id in gap_ids.iter() {
1431 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
1432 }
1433
1434 gap_ids.sort();
1435
1436 for gap_id in gap_ids.iter() {
1437 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1438 assert_eq!(pk_id, *gap_id);
1439 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1440 assert_eq!(prekey.prekey_id.value(), *gap_id);
1441 }
1442
1443 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
1444 gap_ids.sort();
1445 gap_ids.dedup();
1446 while gap_ids.len() < GAP_AMOUNT as usize {
1447 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
1448 gap_ids.sort();
1449 gap_ids.dedup();
1450 }
1451 for gap_id in gap_ids.iter() {
1452 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
1453 }
1454
1455 let potential_range = *ID_TEST_RANGE.end()..=(*ID_TEST_RANGE.end() * 2);
1456 let potential_range_check = potential_range.clone();
1457 for _ in potential_range {
1458 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1459 assert!(gap_ids.contains(&pk_id) || potential_range_check.contains(&pk_id));
1460 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1461 assert_eq!(prekey.prekey_id.value(), pk_id);
1462 }
1463 keystore.commit_transaction().await.unwrap();
1464 keystore.wipe().await.unwrap();
1465 #[cfg(not(target_family = "wasm"))]
1466 drop(db_file);
1467 }
1468
1469 #[cfg(all(feature = "cryptobox-migrate", not(target_family = "wasm")))]
1470 #[async_std::test]
1471 async fn can_import_cryptobox() {
1472 use crate::CryptoboxMigrationErrorKind;
1473
1474 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1475
1476 let cryptobox_folder = tempfile::tempdir().unwrap();
1477 let alice = cryptobox::CBox::file_open(cryptobox_folder.path()).unwrap();
1478 let alice_fingerprint = alice.fingerprint();
1479
1480 let mut bob = CryptoboxLike::init();
1481 let bob_pk_bundle = bob.new_prekey();
1482
1483 let alice_pk_id = proteus::keys::PreKeyId::new(1u16);
1484 let alice_pk = alice.new_prekey(alice_pk_id).unwrap();
1485
1486 let mut alice_session = alice
1487 .session_from_prekey(session_id.clone(), &bob_pk_bundle.serialise().unwrap())
1488 .unwrap();
1489
1490 let message = b"Hello world!";
1491
1492 let alice_msg_envelope = alice_session.encrypt(message).unwrap();
1493 let decrypted = bob.decrypt(&session_id, &alice_msg_envelope).await;
1494 assert_eq!(decrypted, message);
1495
1496 alice.session_save(&mut alice_session).unwrap();
1497
1498 let encrypted = bob.encrypt(&session_id, &message[..]);
1499 let decrypted = alice_session.decrypt(&encrypted).unwrap();
1500 assert_eq!(decrypted, message);
1501
1502 alice.session_save(&mut alice_session).unwrap();
1503
1504 drop(alice);
1505
1506 let keystore_dir = tempfile::tempdir().unwrap();
1507 let keystore_file = keystore_dir.path().join("keystore");
1508
1509 let mut keystore =
1510 core_crypto_keystore::Connection::open_with_key(keystore_file.as_os_str().to_string_lossy(), "test")
1511 .await
1512 .unwrap();
1513 keystore.new_transaction().await.unwrap();
1514
1515 let Err(crate::Error::CryptoboxMigration(crate::CryptoboxMigrationError {
1516 source: CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(_),
1517 ..
1518 })) = ProteusCentral::cryptobox_migrate(&keystore, "invalid path").await
1519 else {
1520 panic!("ProteusCentral::cryptobox_migrate did not throw an error on invalid path");
1521 };
1522
1523 ProteusCentral::cryptobox_migrate(&keystore, &cryptobox_folder.path().to_string_lossy())
1524 .await
1525 .unwrap();
1526
1527 let mut proteus_central = ProteusCentral::try_new(&keystore).await.unwrap();
1528
1529 assert_eq!(proteus_central.fingerprint(), alice_fingerprint);
1531
1532 let alice_new_session_lock = proteus_central
1534 .session(&session_id, &mut keystore)
1535 .await
1536 .unwrap()
1537 .unwrap();
1538 let alice_new_session = alice_new_session_lock.read().await;
1539 assert_eq!(
1540 alice_new_session.session.local_identity().fingerprint(),
1541 alice_session.fingerprint_local()
1542 );
1543 assert_eq!(
1544 alice_new_session.session.remote_identity().fingerprint(),
1545 alice_session.fingerprint_remote()
1546 );
1547
1548 drop(alice_new_session);
1549 drop(alice_new_session_lock);
1550
1551 let keystore_pk = keystore.prekey(1).await.unwrap().unwrap();
1553 let keystore_pk = proteus_wasm::keys::PreKey::deserialise(&keystore_pk).unwrap();
1554 assert_eq!(alice_pk.prekey_id.value(), keystore_pk.key_id.value());
1555 assert_eq!(
1556 alice_pk.public_key.fingerprint(),
1557 keystore_pk.key_pair.public_key.fingerprint()
1558 );
1559
1560 let encrypted = proteus_central
1562 .encrypt(&mut keystore, &session_id, &message[..])
1563 .await
1564 .unwrap();
1565 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1566
1567 assert_eq!(&decrypted, &message[..]);
1568
1569 let encrypted = bob.encrypt(&session_id, &message[..]);
1574 let decrypted = proteus_central
1575 .decrypt(&mut keystore, &session_id, &encrypted)
1576 .await
1577 .unwrap();
1578 assert_eq!(&decrypted, &message[..]);
1579
1580 proteus_central.session_save(&mut keystore, &session_id).await.unwrap();
1581 keystore.commit_transaction().await.unwrap();
1582 keystore.wipe().await.unwrap();
1583 }
1584
1585 cfg_if::cfg_if! {
1586 if #[cfg(all(feature = "cryptobox-migrate", target_family = "wasm"))] {
1587 const CRYPTOBOX_JS_DBNAME: &str = "cryptobox-migrate-test";
1589 fn run_cryptobox(alice: CryptoboxLike) -> js_sys::Promise {
1593 wasm_bindgen_futures::future_to_promise(async move {
1594 use rexie::{Rexie, ObjectStore, TransactionMode};
1595 use wasm_bindgen::JsValue;
1596
1597 Rexie::builder(CRYPTOBOX_JS_DBNAME)
1599 .delete()
1600 .await.map_err(|err| err.to_string())?;
1601
1602 let rexie = Rexie::builder(CRYPTOBOX_JS_DBNAME)
1603 .version(1)
1604 .add_object_store(ObjectStore::new("keys").auto_increment(false))
1605 .add_object_store(ObjectStore::new("prekeys").auto_increment(false))
1606 .add_object_store(ObjectStore::new("sessions").auto_increment(false))
1607 .build()
1608 .await.map_err(|err| err.to_string())?;
1609
1610 let transaction = rexie.transaction(&["keys"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1612 let store = transaction.store("keys").map_err(|err| err.to_string())?;
1613
1614 use base64::Engine as _;
1615 let json = serde_json::json!({
1616 "created": 0,
1617 "id": "local_identity",
1618 "serialised": base64::prelude::BASE64_STANDARD.encode(alice.identity.serialise().unwrap()),
1619 "version": "1.0"
1620 });
1621 let js_value = serde_wasm_bindgen::to_value(&json)?;
1622
1623 store.add(&js_value, Some(&JsValue::from_str("local_identity"))).await.map_err(|err| err.to_string())?;
1624
1625 let transaction = rexie.transaction(&["prekeys"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1627 let store = transaction.store("prekeys").map_err(|err| err.to_string())?;
1628 for prekey in alice.prekeys.0.into_iter() {
1629 let id = prekey.key_id.value().to_string();
1630 let json = serde_json::json!({
1631 "created": 0,
1632 "id": &id,
1633 "serialised": base64::prelude::BASE64_STANDARD.encode(prekey.serialise().unwrap()),
1634 "version": "1.0"
1635 });
1636 let js_value = serde_wasm_bindgen::to_value(&json)?;
1637 store.add(&js_value, Some(&JsValue::from_str(&id))).await.map_err(|err| err.to_string())?;
1638 }
1639
1640 let transaction = rexie.transaction(&["sessions"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1642 let store = transaction.store("sessions").map_err(|err| err.to_string())?;
1643 for (session_id, session) in alice.sessions.into_iter() {
1644 let json = serde_json::json!({
1645 "created": 0,
1646 "id": session_id,
1647 "serialised": base64::prelude::BASE64_STANDARD.encode(session.serialise().unwrap()),
1648 "version": "1.0"
1649 });
1650
1651 let js_value = serde_wasm_bindgen::to_value(&json)?;
1652 store.add(&js_value, Some(&JsValue::from_str(&session_id))).await.map_err(|err| err.to_string())?;
1653 }
1654
1655 Ok(JsValue::UNDEFINED)
1656 })
1657 }
1658
1659 #[wasm_bindgen_test]
1660 async fn can_import_cryptobox() {
1661 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1662
1663 let mut alice = CryptoboxLike::init();
1664 let alice_fingerprint = alice.fingerprint();
1665 const PREKEY_COUNT: usize = 10;
1666 let prekey_iter_range = 0..PREKEY_COUNT;
1667 let prekey_bundles: Vec<proteus_wasm::keys::PreKeyBundle> = prekey_iter_range.clone().map(|_| alice.new_prekey()).collect();
1669
1670 let mut bob = CryptoboxLike::init();
1672 let bob_pk_bundle = bob.new_prekey();
1673 let message = b"Hello world!";
1674
1675 alice.init_session_from_prekey_bundle(&session_id, &bob_pk_bundle.serialise().unwrap());
1676 let alice_to_bob_message = alice.encrypt(&session_id, message);
1677 let decrypted = bob.decrypt(&session_id, &alice_to_bob_message).await;
1678 assert_eq!(&message[..], decrypted.as_slice());
1679
1680 let bob_to_alice_message = bob.encrypt(&session_id, message);
1681 let decrypted = alice.decrypt(&session_id, &bob_to_alice_message).await;
1682 assert_eq!(&message[..], decrypted.as_slice());
1683
1684 let alice_session = alice.session(&session_id);
1685 let alice_session_fingerprint_local = alice_session.local_identity().fingerprint();
1686 let alice_session_fingerprint_remote = alice_session.remote_identity().fingerprint();
1687
1688 let _ = wasm_bindgen_futures::JsFuture::from(run_cryptobox(alice)).await.unwrap();
1689 let mut keystore = core_crypto_keystore::Connection::open_with_key(&format!("{CRYPTOBOX_JS_DBNAME}-imported"), "test").await.unwrap();
1690 keystore.new_transaction().await.unwrap();
1691 let Err(crate::Error::CryptoboxMigration(crate::CryptoboxMigrationError{
1692 source: crate::CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(_),
1693 ..
1694 })) = ProteusCentral::cryptobox_migrate(&keystore, "invalid path").await else {
1695 panic!("ProteusCentral::cryptobox_migrate did not throw an error on invalid path");
1696 };
1697
1698 ProteusCentral::cryptobox_migrate(&keystore, CRYPTOBOX_JS_DBNAME).await.unwrap();
1699
1700 let mut proteus_central = ProteusCentral::try_new(&keystore).await.unwrap();
1701
1702 assert_eq!(proteus_central.fingerprint(), alice_fingerprint);
1704
1705 let alice_new_session_lock = proteus_central
1707 .session(&session_id, &mut keystore)
1708 .await
1709 .unwrap()
1710 .unwrap();
1711 let alice_new_session = alice_new_session_lock.read().await;
1712 assert_eq!(
1713 alice_new_session.session.local_identity().fingerprint(),
1714 alice_session_fingerprint_local
1715 );
1716 assert_eq!(
1717 alice_new_session.session.remote_identity().fingerprint(),
1718 alice_session_fingerprint_remote
1719 );
1720
1721 drop(alice_new_session);
1722 drop(alice_new_session_lock);
1723
1724 for i in prekey_iter_range {
1726 let prekey_id = (i + 1) as u16;
1727 let keystore_pk = keystore.prekey(prekey_id).await.unwrap().unwrap();
1728 let keystore_pk = proteus_wasm::keys::PreKey::deserialise(&keystore_pk).unwrap();
1729 let alice_pk = &prekey_bundles[i];
1730
1731 assert_eq!(alice_pk.prekey_id.value(), keystore_pk.key_id.value());
1732 assert_eq!(
1733 alice_pk.public_key.fingerprint(),
1734 keystore_pk.key_pair.public_key.fingerprint()
1735 );
1736 }
1737
1738
1739 let encrypted = proteus_central.encrypt(&mut keystore, &session_id, &message[..]).await.unwrap();
1741 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1742
1743 assert_eq!(&decrypted, &message[..]);
1744
1745 let encrypted = bob.encrypt(&session_id, &message[..]);
1747 let decrypted = proteus_central
1748 .decrypt(&mut keystore, &session_id, &encrypted)
1749 .await
1750 .unwrap();
1751 assert_eq!(&decrypted, &message[..]);
1752 keystore.commit_transaction().await.unwrap();
1753
1754 keystore.wipe().await.unwrap();
1755 }
1756 }
1757 }
1758}