1use crate::{
2 CoreCrypto, Error, KeystoreError, LeafError, ProteusError, Result,
3 group_store::{GroupStore, GroupStoreEntity, GroupStoreValue},
4};
5use core_crypto_keystore::{
6 Connection 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(
39 &mut self,
40 store: &mut core_crypto_keystore::Connection,
41 ciphertext: &[u8],
42 ) -> Result<Vec<u8>> {
43 let envelope = Envelope::deserialise(ciphertext).map_err(ProteusError::wrap("deserializing envelope"))?;
44 self.session
45 .decrypt(store, &envelope)
46 .await
47 .map_err(ProteusError::wrap("decrypting message for proteus session"))
48 .map_err(Into::into)
49 }
50
51 pub fn identifier(&self) -> &str {
53 &self.identifier
54 }
55
56 pub fn fingerprint_local(&self) -> String {
58 self.session.local_identity().fingerprint()
59 }
60
61 pub fn fingerprint_remote(&self) -> String {
63 self.session.remote_identity().fingerprint()
64 }
65}
66
67#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
68#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
69impl GroupStoreEntity for ProteusConversationSession {
70 type RawStoreValue = core_crypto_keystore::entities::ProteusSession;
71 type IdentityType = Arc<proteus_wasm::keys::IdentityKeyPair>;
72
73 #[cfg(test)]
74 fn id(&self) -> &[u8] {
75 unreachable!()
76 }
77
78 async fn fetch_from_id(
79 id: &[u8],
80 identity: Option<Self::IdentityType>,
81 keystore: &impl FetchFromDatabase,
82 ) -> crate::Result<Option<Self>> {
83 let result = keystore
84 .find::<Self::RawStoreValue>(id)
85 .await
86 .map_err(KeystoreError::wrap("finding raw group store entity by id"))?;
87 let Some(store_value) = result else {
88 return Ok(None);
89 };
90
91 let Some(identity) = identity else {
92 return Err(crate::Error::ProteusNotInitialized);
93 };
94
95 let session = proteus_wasm::session::Session::deserialise(identity, &store_value.session)
96 .map_err(ProteusError::wrap("deserializing session"))?;
97
98 Ok(Some(Self {
99 identifier: store_value.id.clone(),
100 session,
101 }))
102 }
103
104 #[cfg(test)]
105 async fn fetch_all(_keystore: &impl FetchFromDatabase) -> Result<Vec<Self>>
106 where
107 Self: Sized,
108 {
109 unreachable!()
110 }
111}
112
113impl CoreCrypto {
114 pub async fn proteus_session(
120 &self,
121 session_id: &str,
122 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
123 let mut mutex = self.proteus.lock().await;
124 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
125 let keystore = self.mls.crypto_provider.keystore();
126 proteus.session(session_id, &keystore).await
127 }
128
129 pub async fn proteus_session_exists(&self, session_id: &str) -> Result<bool> {
135 let mut mutex = self.proteus.lock().await;
136 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
137 let keystore = self.mls.crypto_provider.keystore();
138 Ok(proteus.session_exists(session_id, &keystore).await)
139 }
140
141 pub fn proteus_last_resort_prekey_id() -> u16 {
143 ProteusCentral::last_resort_prekey_id()
144 }
145
146 pub async fn proteus_fingerprint(&self) -> Result<String> {
152 let mutex = self.proteus.lock().await;
153 let proteus = mutex.as_ref().ok_or(Error::ProteusNotInitialized)?;
154 Ok(proteus.fingerprint())
155 }
156
157 pub async fn proteus_fingerprint_local(&self, session_id: &str) -> Result<String> {
163 let mut mutex = self.proteus.lock().await;
164 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
165 let keystore = self.mls.crypto_provider.keystore();
166 proteus.fingerprint_local(session_id, &keystore).await
167 }
168
169 pub async fn proteus_fingerprint_remote(&self, session_id: &str) -> Result<String> {
175 let mut mutex = self.proteus.lock().await;
176 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
177 let keystore = self.mls.crypto_provider.keystore();
178 proteus.fingerprint_remote(session_id, &keystore).await
179 }
180}
181
182#[derive(Debug)]
187pub struct ProteusCentral {
188 proteus_identity: Arc<IdentityKeyPair>,
189 proteus_sessions: GroupStore<ProteusConversationSession>,
190}
191
192impl ProteusCentral {
193 pub async fn try_new(keystore: &CryptoKeystore) -> Result<Self> {
195 let proteus_identity: Arc<IdentityKeyPair> = Arc::new(Self::load_or_create_identity(keystore).await?);
196 let proteus_sessions = Self::restore_sessions(keystore, &proteus_identity).await?;
197
198 Ok(Self {
199 proteus_identity,
200 proteus_sessions,
201 })
202 }
203
204 pub(crate) async fn reload_sessions(&mut self, keystore: &CryptoKeystore) -> Result<()> {
206 self.proteus_sessions = Self::restore_sessions(keystore, &self.proteus_identity).await?;
207 Ok(())
208 }
209
210 async fn load_or_create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
213 let Some(identity) = keystore
214 .find::<ProteusIdentity>(&[])
215 .await
216 .map_err(KeystoreError::wrap("finding proteus identity"))?
217 else {
218 return Self::create_identity(keystore).await;
219 };
220
221 let sk = identity.sk_raw();
222 let pk = identity.pk_raw();
223
224 IdentityKeyPair::from_raw_key_pair(*sk, *pk)
226 .map_err(ProteusError::wrap("constructing identity keypair"))
227 .map_err(Into::into)
228 }
229
230 async fn create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
232 let kp = IdentityKeyPair::new();
233 let pk = kp.public_key.public_key.as_slice().to_vec();
234
235 let ks_identity = ProteusIdentity {
236 sk: kp.secret_key.to_keypair_bytes().into(),
237 pk,
238 };
239 keystore
240 .save(ks_identity)
241 .await
242 .map_err(KeystoreError::wrap("saving new proteus identity"))?;
243
244 Ok(kp)
245 }
246
247 async fn restore_sessions(
249 keystore: &core_crypto_keystore::Connection,
250 identity: &Arc<IdentityKeyPair>,
251 ) -> Result<GroupStore<ProteusConversationSession>> {
252 let mut proteus_sessions = GroupStore::new_with_limit(crate::group_store::ITEM_LIMIT * 2);
253 for session in keystore
254 .find_all::<ProteusSession>(Default::default())
255 .await
256 .map_err(KeystoreError::wrap("finding all proteus sessions"))?
257 .into_iter()
258 {
259 let proteus_session = Session::deserialise(identity.clone(), &session.session)
260 .map_err(ProteusError::wrap("deserializing session"))?;
261
262 let identifier = session.id.clone();
263
264 let proteus_conversation = ProteusConversationSession {
265 identifier: identifier.clone(),
266 session: proteus_session,
267 };
268
269 if proteus_sessions
270 .try_insert(identifier.into_bytes(), proteus_conversation)
271 .is_err()
272 {
273 break;
274 }
275 }
276
277 Ok(proteus_sessions)
278 }
279
280 pub async fn session_from_prekey(
282 &mut self,
283 session_id: &str,
284 key: &[u8],
285 ) -> Result<GroupStoreValue<ProteusConversationSession>> {
286 let prekey = PreKeyBundle::deserialise(key).map_err(ProteusError::wrap("deserializing prekey bundle"))?;
287 let proteus_session = Session::init_from_prekey::<core_crypto_keystore::CryptoKeystoreError>(
319 self.proteus_identity.clone(),
320 prekey,
321 )
322 .map_err(ProteusError::wrap("initializing session from prekey"))?;
323
324 let proteus_conversation = ProteusConversationSession {
325 identifier: session_id.into(),
326 session: proteus_session,
327 };
328
329 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
330
331 Ok(self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone())
332 }
333
334 pub(crate) async fn session_from_message(
336 &mut self,
337 keystore: &mut CryptoKeystore,
338 session_id: &str,
339 envelope: &[u8],
340 ) -> Result<(GroupStoreValue<ProteusConversationSession>, Vec<u8>)> {
341 let message = Envelope::deserialise(envelope).map_err(ProteusError::wrap("deserialising envelope"))?;
342 let (session, payload) = Session::init_from_message(self.proteus_identity.clone(), keystore, &message)
343 .await
344 .map_err(ProteusError::wrap("initializing session from message"))?;
345
346 let proteus_conversation = ProteusConversationSession {
347 identifier: session_id.into(),
348 session,
349 };
350
351 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
352
353 Ok((
354 self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone(),
355 payload,
356 ))
357 }
358
359 pub(crate) async fn session_save(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
363 if let Some(session) = self
364 .proteus_sessions
365 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
366 .await?
367 {
368 Self::session_save_by_ref(keystore, session).await?;
369 }
370
371 Ok(())
372 }
373
374 pub(crate) async fn session_save_by_ref(
375 keystore: &CryptoKeystore,
376 session: GroupStoreValue<ProteusConversationSession>,
377 ) -> Result<()> {
378 let session = session.read().await;
379 let db_session = ProteusSession {
380 id: session.identifier().to_string(),
381 session: session
382 .session
383 .serialise()
384 .map_err(ProteusError::wrap("serializing session"))?,
385 };
386 keystore
387 .save(db_session)
388 .await
389 .map_err(KeystoreError::wrap("saving proteus session"))?;
390 Ok(())
391 }
392
393 pub(crate) async fn session_delete(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
395 if keystore.remove::<ProteusSession, _>(session_id).await.is_ok() {
396 let _ = self.proteus_sessions.remove(session_id.as_bytes());
397 }
398 Ok(())
399 }
400
401 pub(crate) async fn session(
403 &mut self,
404 session_id: &str,
405 keystore: &CryptoKeystore,
406 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
407 self.proteus_sessions
408 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
409 .await
410 }
411
412 pub(crate) async fn session_exists(&mut self, session_id: &str, keystore: &CryptoKeystore) -> bool {
414 self.session(session_id, keystore).await.ok().flatten().is_some()
415 }
416
417 pub(crate) async fn decrypt(
420 &mut self,
421 keystore: &mut CryptoKeystore,
422 session_id: &str,
423 ciphertext: &[u8],
424 ) -> Result<Vec<u8>> {
425 let session = self
426 .proteus_sessions
427 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
428 .await?
429 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
430 .map_err(ProteusError::wrap("getting session"))?;
431
432 let plaintext = session.write().await.decrypt(keystore, ciphertext).await?;
433 ProteusCentral::session_save_by_ref(keystore, session).await?;
434
435 Ok(plaintext)
436 }
437
438 pub(crate) async fn encrypt(
440 &mut self,
441 keystore: &mut CryptoKeystore,
442 session_id: &str,
443 plaintext: &[u8],
444 ) -> Result<Vec<u8>> {
445 let session = self
446 .session(session_id, keystore)
447 .await?
448 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
449 .map_err(ProteusError::wrap("getting session"))?;
450
451 let ciphertext = session.write().await.encrypt(plaintext)?;
452 ProteusCentral::session_save_by_ref(keystore, session).await?;
453
454 Ok(ciphertext)
455 }
456
457 pub(crate) async fn encrypt_batched(
460 &mut self,
461 keystore: &mut CryptoKeystore,
462 sessions: &[impl AsRef<str>],
463 plaintext: &[u8],
464 ) -> Result<HashMap<String, Vec<u8>>> {
465 let mut acc = HashMap::new();
466 for session_id in sessions {
467 if let Some(session) = self.session(session_id.as_ref(), keystore).await? {
468 let mut session_w = session.write().await;
469 acc.insert(session_w.identifier.clone(), session_w.encrypt(plaintext)?);
470 drop(session_w);
471
472 ProteusCentral::session_save_by_ref(keystore, session).await?;
473 }
474 }
475 Ok(acc)
476 }
477
478 pub(crate) async fn new_prekey(&self, id: u16, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
480 use proteus_wasm::keys::{PreKey, PreKeyId};
481
482 let prekey_id = PreKeyId::new(id);
483 let prekey = PreKey::new(prekey_id);
484 let keystore_prekey = core_crypto_keystore::entities::ProteusPrekey::from_raw(
485 id,
486 prekey.serialise().map_err(ProteusError::wrap("serialising prekey"))?,
487 );
488 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &prekey);
489 let bundle = bundle
490 .serialise()
491 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
492 keystore
493 .save(keystore_prekey)
494 .await
495 .map_err(KeystoreError::wrap("saving keystore prekey"))?;
496 Ok(bundle)
497 }
498
499 pub(crate) async fn new_prekey_auto(&self, keystore: &CryptoKeystore) -> Result<(u16, Vec<u8>)> {
503 let id = core_crypto_keystore::entities::ProteusPrekey::get_free_id(keystore)
504 .await
505 .map_err(KeystoreError::wrap("getting proteus prekey by id"))?;
506 Ok((id, self.new_prekey(id, keystore).await?))
507 }
508
509 pub fn last_resort_prekey_id() -> u16 {
511 proteus_wasm::keys::MAX_PREKEY_ID.value()
512 }
513
514 pub(crate) async fn last_resort_prekey(&self, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
517 let last_resort = if let Some(last_resort) = keystore
518 .find::<core_crypto_keystore::entities::ProteusPrekey>(
519 Self::last_resort_prekey_id().to_le_bytes().as_slice(),
520 )
521 .await
522 .map_err(KeystoreError::wrap("finding proteus prekey"))?
523 {
524 proteus_wasm::keys::PreKey::deserialise(&last_resort.prekey)
525 .map_err(ProteusError::wrap("deserialising proteus prekey"))?
526 } else {
527 let last_resort = proteus_wasm::keys::PreKey::last_resort();
528
529 use core_crypto_keystore::CryptoKeystoreProteus as _;
530 keystore
531 .proteus_store_prekey(
532 Self::last_resort_prekey_id(),
533 &last_resort
534 .serialise()
535 .map_err(ProteusError::wrap("serialising last resort prekey"))?,
536 )
537 .await
538 .map_err(KeystoreError::wrap("storing proteus prekey"))?;
539
540 last_resort
541 };
542
543 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &last_resort);
544 let bundle = bundle
545 .serialise()
546 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
547
548 Ok(bundle)
549 }
550
551 pub fn identity(&self) -> &IdentityKeyPair {
553 self.proteus_identity.as_ref()
554 }
555
556 pub fn fingerprint(&self) -> String {
558 self.proteus_identity.as_ref().public_key.fingerprint()
559 }
560
561 pub(crate) async fn fingerprint_local(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
566 let session = self
567 .session(session_id, keystore)
568 .await?
569 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
570 .map_err(ProteusError::wrap("getting session"))?;
571 let fingerprint = session.read().await.fingerprint_local();
572 Ok(fingerprint)
573 }
574
575 pub(crate) async fn fingerprint_remote(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
580 let session = self
581 .session(session_id, keystore)
582 .await?
583 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
584 .map_err(ProteusError::wrap("getting session"))?;
585 let fingerprint = session.read().await.fingerprint_remote();
586 Ok(fingerprint)
587 }
588
589 pub fn fingerprint_prekeybundle(prekey: &[u8]) -> Result<String> {
594 let prekey = PreKeyBundle::deserialise(prekey).map_err(ProteusError::wrap("deserialising prekey bundle"))?;
595 Ok(prekey.identity_key.fingerprint())
596 }
597
598 #[cfg_attr(not(feature = "cryptobox-migrate"), allow(unused_variables))]
600 pub(crate) async fn cryptobox_migrate(keystore: &CryptoKeystore, path: &str) -> Result<()> {
601 cfg_if::cfg_if! {
602 if #[cfg(feature = "cryptobox-migrate")] {
603 Self::cryptobox_migrate_impl(keystore, path).await?;
604 Ok(())
605 } else {
606 Err(Error::FeatureDisabled("cryptobox-migrate"))
607 }
608 }
609 }
610}
611
612#[cfg(feature = "cryptobox-migrate")]
613#[allow(dead_code)]
614impl ProteusCentral {
615 #[cfg(not(target_family = "wasm"))]
616 async fn cryptobox_migrate_impl(keystore: &CryptoKeystore, path: &str) -> Result<()> {
617 let root_dir = std::path::PathBuf::from(path);
618
619 if !root_dir.exists() {
620 return Err(CryptoboxMigrationError::wrap("root dir does not exist")(
621 crate::CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(path.into()),
622 )
623 .into());
624 }
625
626 let session_dir = root_dir.join("sessions");
627 let prekey_dir = root_dir.join("prekeys");
628
629 let missing_identity = Err(CryptoboxMigrationError::wrap("taking identity keypair")(
631 crate::CryptoboxMigrationErrorKind::IdentityNotFound(path.into()),
632 )
633 .into());
634
635 let identity = if let Some(store_kp) = keystore
636 .find::<ProteusIdentity>(&[])
637 .await
638 .map_err(KeystoreError::wrap("finding proteus identity"))?
639 {
640 Box::new(
641 IdentityKeyPair::from_raw_key_pair(*store_kp.sk_raw(), *store_kp.pk_raw())
642 .map_err(ProteusError::wrap("constructing identity keypair from raw keypair"))?,
643 )
644 } else {
645 let identity_dir = root_dir.join("identities");
646
647 let identity = identity_dir.join("local");
648 let legacy_identity = identity_dir.join("local_identity");
649 let kp = if legacy_identity.exists() {
651 let kp_cbor = async_fs::read(&legacy_identity)
652 .await
653 .map_err(CryptoboxMigrationError::wrap("reading legacy identity from filesystem"))?;
654 let kp = IdentityKeyPair::deserialise(&kp_cbor)
655 .map_err(ProteusError::wrap("deserialising identity keypair"))?;
656
657 Box::new(kp)
658 } else if identity.exists() {
659 let kp_cbor = async_fs::read(&identity)
660 .await
661 .map_err(CryptoboxMigrationError::wrap("reading identity from filesystem"))?;
662 let kp = proteus_wasm::identity::Identity::deserialise(&kp_cbor)
663 .map_err(ProteusError::wrap("deserialising identity"))?;
664
665 if let proteus_wasm::identity::Identity::Sec(kp) = kp {
666 kp.into_owned()
667 } else {
668 return missing_identity;
669 }
670 } else {
671 return missing_identity;
672 };
673
674 let pk = kp.public_key.public_key.as_slice().into();
675
676 let ks_identity = ProteusIdentity {
677 sk: kp.secret_key.to_keypair_bytes().into(),
678 pk,
679 };
680
681 keystore
682 .save(ks_identity)
683 .await
684 .map_err(KeystoreError::wrap("saving proteus identity"))?;
685
686 if legacy_identity.exists() {
687 async_fs::remove_file(legacy_identity)
688 .await
689 .map_err(CryptoboxMigrationError::wrap("removing legacy identity"))?;
690 }
691
692 kp
693 };
694
695 let identity = *identity;
696
697 use futures_lite::stream::StreamExt as _;
698 let mut session_entries = async_fs::read_dir(session_dir)
700 .await
701 .map_err(CryptoboxMigrationError::wrap("reading session entries"))?;
702 while let Some(session_file) = session_entries
703 .try_next()
704 .await
705 .map_err(CryptoboxMigrationError::wrap("getting next session file"))?
706 {
707 let proteus_session_id: String = session_file.file_name().to_string_lossy().to_string();
709
710 if keystore
712 .find::<ProteusSession>(proteus_session_id.as_bytes())
713 .await
714 .map_err(KeystoreError::wrap("finding proteus session by id"))?
715 .is_some()
716 {
717 continue;
718 }
719
720 let raw_session = async_fs::read(session_file.path())
721 .await
722 .map_err(CryptoboxMigrationError::wrap("reading session file"))?;
723 let Ok(_) = Session::deserialise(&identity, &raw_session) else {
725 continue;
726 };
727
728 let keystore_session = ProteusSession {
729 id: proteus_session_id,
730 session: raw_session,
731 };
732
733 keystore
734 .save(keystore_session)
735 .await
736 .map_err(KeystoreError::wrap("saving proteus session"))?;
737 }
738
739 use core_crypto_keystore::entities::ProteusPrekey;
741
742 use crate::CryptoboxMigrationError;
743 let mut prekey_entries = async_fs::read_dir(prekey_dir)
744 .await
745 .map_err(CryptoboxMigrationError::wrap("reading prekey entries"))?;
746 while let Some(prekey_file) = prekey_entries
747 .try_next()
748 .await
749 .map_err(CryptoboxMigrationError::wrap("getting next prekey file"))?
750 {
751 let proteus_prekey_id = proteus_wasm::keys::PreKeyId::new(
753 prekey_file
754 .file_name()
755 .to_string_lossy()
756 .parse()
757 .map_err(CryptoboxMigrationError::wrap("parsing prekey file name"))?,
758 );
759
760 if keystore
762 .find::<ProteusPrekey>(&proteus_prekey_id.value().to_le_bytes())
763 .await
764 .map_err(KeystoreError::wrap("finding proteus prekey by id"))?
765 .is_some()
766 {
767 continue;
768 }
769
770 let raw_prekey = async_fs::read(prekey_file.path())
771 .await
772 .map_err(CryptoboxMigrationError::wrap("reading prekey file"))?;
773 if proteus_wasm::keys::PreKey::deserialise(&raw_prekey).is_ok() {
775 let keystore_prekey = ProteusPrekey::from_raw(proteus_prekey_id.value(), raw_prekey);
776 keystore
777 .save(keystore_prekey)
778 .await
779 .map_err(KeystoreError::wrap("saving proteus prekey"))?;
780 }
781 }
782
783 Ok(())
784 }
785
786 #[cfg(target_family = "wasm")]
787 fn get_cbor_bytes_from_map(map: serde_json::map::Map<String, serde_json::Value>) -> Result<Vec<u8>> {
788 use crate::{CryptoboxMigrationError, CryptoboxMigrationErrorKind};
789
790 let Some(js_value) = map.get("serialised") else {
791 return Err(CryptoboxMigrationError::wrap("getting serialised cbor bytes from map")(
792 CryptoboxMigrationErrorKind::MissingKeyInValue("serialised".to_string()),
793 )
794 .into());
795 };
796
797 let Some(b64_value) = js_value.as_str() else {
798 return Err(CryptoboxMigrationError::wrap("getting js value as string")(
799 CryptoboxMigrationErrorKind::WrongValueType("string".to_string()),
800 )
801 .into());
802 };
803
804 use base64::Engine as _;
805 let cbor_bytes = base64::prelude::BASE64_STANDARD
806 .decode(b64_value)
807 .map_err(CryptoboxMigrationError::wrap("decoding cbor bytes"))?;
808 Ok(cbor_bytes)
809 }
810
811 #[cfg(target_family = "wasm")]
812 async fn cryptobox_migrate_impl(keystore: &CryptoKeystore, path: &str) -> Result<()> {
813 use rexie::{Rexie, TransactionMode};
814
815 use crate::{CryptoboxMigrationError, CryptoboxMigrationErrorKind};
816 let local_identity_key = "local_identity";
817 let local_identity_store_name = "keys";
818 let prekeys_store_name = "prekeys";
819 let sessions_store_name = "sessions";
820
821 let db = Rexie::builder(path)
823 .build()
824 .await
825 .map_err(CryptoboxMigrationError::wrap("building rexie"))?;
826
827 let store_names = db.store_names();
828
829 let expected_stores = &[prekeys_store_name, sessions_store_name, local_identity_store_name];
830
831 if !expected_stores
832 .iter()
833 .map(ToString::to_string)
834 .all(|s| store_names.contains(&s))
835 {
836 return Err(CryptoboxMigrationError::wrap("checking expected stores")(
837 CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(path.into()),
838 )
839 .into());
840 }
841
842 let mut proteus_identity = if let Some(store_kp) = keystore
843 .find::<ProteusIdentity>(&[])
844 .await
845 .map_err(KeystoreError::wrap("finding proteus identity for empty id"))?
846 {
847 Some(
848 proteus_wasm::keys::IdentityKeyPair::from_raw_key_pair(*store_kp.sk_raw(), *store_kp.pk_raw())
849 .map_err(ProteusError::wrap("constructing identity keypair from raw"))?,
850 )
851 } else {
852 let transaction = db
853 .transaction(&[local_identity_store_name], TransactionMode::ReadOnly)
854 .map_err(CryptoboxMigrationError::wrap("initializing rexie transaction"))?;
855
856 let identity_store = transaction
857 .store(local_identity_store_name)
858 .map_err(CryptoboxMigrationError::wrap("storing local identity store name"))?;
859
860 if let Some(cryptobox_js_value) = identity_store
861 .get(local_identity_key.into())
862 .await
863 .map_err(CryptoboxMigrationError::wrap("getting local identity key js value"))?
864 {
865 let js_value: serde_json::map::Map<String, serde_json::Value> =
866 serde_wasm_bindgen::from_value(cryptobox_js_value).map_err(CryptoboxMigrationError::wrap(
867 "getting local identity key from identity store",
868 ))?;
869
870 let kp_cbor = Self::get_cbor_bytes_from_map(js_value)?;
871
872 let kp = proteus_wasm::keys::IdentityKeyPair::deserialise(&kp_cbor)
873 .map_err(ProteusError::wrap("deserializing identity keypair"))?;
874
875 let pk = kp.public_key.public_key.as_slice().to_vec();
876
877 let ks_identity = ProteusIdentity {
878 sk: kp.secret_key.to_keypair_bytes().into(),
879 pk,
880 };
881 keystore
882 .save(ks_identity)
883 .await
884 .map_err(KeystoreError::wrap("saving proteus identity in keystore"))?;
885
886 Some(kp)
887 } else {
888 None
889 }
890 };
891
892 let Some(proteus_identity) = proteus_identity.take() else {
893 return Err(CryptoboxMigrationError::wrap("taking proteus identity")(
894 CryptoboxMigrationErrorKind::IdentityNotFound(path.into()),
895 )
896 .into());
897 };
898
899 if store_names.contains(&sessions_store_name.to_string()) {
900 let transaction = db
901 .transaction(&[sessions_store_name], TransactionMode::ReadOnly)
902 .map_err(CryptoboxMigrationError::wrap("starting rexie transaction"))?;
903
904 let sessions_store = transaction
905 .store(sessions_store_name)
906 .map_err(CryptoboxMigrationError::wrap("getting sessions store"))?;
907
908 let sessions = sessions_store
909 .scan(None, None, None, None)
910 .await
911 .map_err(CryptoboxMigrationError::wrap("scanning sessions store for sessions"))?;
912
913 for (session_id, session_js_value) in sessions.into_iter().map(|(k, v)| (k.as_string().unwrap(), v)) {
914 if keystore
916 .find::<ProteusSession>(session_id.as_bytes())
917 .await
918 .map_err(KeystoreError::wrap("finding proteus session by id"))?
919 .is_some()
920 {
921 continue;
922 }
923
924 let js_value: serde_json::map::Map<String, serde_json::Value> =
925 serde_wasm_bindgen::from_value(session_js_value).map_err(CryptoboxMigrationError::wrap(
926 "converting session js value to serde map",
927 ))?;
928
929 let session_cbor_bytes = Self::get_cbor_bytes_from_map(js_value)?;
930
931 if proteus_wasm::session::Session::deserialise(&proteus_identity, &session_cbor_bytes).is_ok() {
933 let keystore_session = ProteusSession {
934 id: session_id,
935 session: session_cbor_bytes,
936 };
937
938 keystore
939 .save(keystore_session)
940 .await
941 .map_err(KeystoreError::wrap("saving keystore session"))?;
942 }
943 }
944 }
945
946 if store_names.contains(&prekeys_store_name.to_string()) {
947 use core_crypto_keystore::entities::ProteusPrekey;
948
949 let transaction = db
950 .transaction(&[prekeys_store_name], TransactionMode::ReadOnly)
951 .map_err(CryptoboxMigrationError::wrap("beginning rexie transaction"))?;
952
953 let prekeys_store = transaction
954 .store(prekeys_store_name)
955 .map_err(CryptoboxMigrationError::wrap("getting prekeys store"))?;
956
957 let prekeys = prekeys_store
958 .scan(None, None, None, None)
959 .await
960 .map_err(CryptoboxMigrationError::wrap("scanning for prekeys"))?;
961
962 for (prekey_id, prekey_js_value) in prekeys
963 .into_iter()
964 .map(|(id, prekey_js_value)| (id.as_string().unwrap(), prekey_js_value))
965 {
966 let prekey_id: u16 = prekey_id
967 .parse()
968 .map_err(CryptoboxMigrationError::wrap("parsing prekey id"))?;
969
970 if keystore
972 .find::<ProteusPrekey>(&prekey_id.to_le_bytes())
973 .await
974 .map_err(KeystoreError::wrap(
975 "finding proteus prekey by id to check for existence",
976 ))?
977 .is_some()
978 {
979 continue;
980 }
981
982 let js_value: serde_json::map::Map<String, serde_json::Value> =
983 serde_wasm_bindgen::from_value(prekey_js_value)
984 .map_err(CryptoboxMigrationError::wrap("converting prekey js value to serde map"))?;
985
986 let raw_prekey_cbor = Self::get_cbor_bytes_from_map(js_value)?;
987
988 if proteus_wasm::keys::PreKey::deserialise(&raw_prekey_cbor).is_ok() {
990 let keystore_prekey = ProteusPrekey::from_raw(prekey_id, raw_prekey_cbor);
991 keystore
992 .save(keystore_prekey)
993 .await
994 .map_err(KeystoreError::wrap("saving proteus prekey"))?;
995 }
996 }
997 }
998
999 Ok(())
1000 }
1001}
1002
1003#[cfg(test)]
1004mod tests {
1005 use crate::{
1006 prelude::{CertificateBundle, ClientIdentifier, MlsClientConfiguration, MlsCredentialType, Session},
1007 test_utils::{proteus_utils::*, x509::X509TestChain, *},
1008 };
1009
1010 use crate::prelude::INITIAL_KEYING_MATERIAL_COUNT;
1011 use proteus_traits::PreKeyStore;
1012 use wasm_bindgen_test::*;
1013
1014 use super::*;
1015
1016 wasm_bindgen_test_configure!(run_in_browser);
1017
1018 use core_crypto_keystore::DatabaseKey;
1019
1020 #[apply(all_cred_cipher)]
1021 #[wasm_bindgen_test]
1022 async fn cc_can_init(case: TestContext) {
1023 #[cfg(not(target_family = "wasm"))]
1024 let (path, db_file) = tmp_db_file();
1025 #[cfg(target_family = "wasm")]
1026 let (path, _) = tmp_db_file();
1027 let client_id = "alice".into();
1028 let cfg = MlsClientConfiguration::try_new(
1029 path,
1030 DatabaseKey::generate(),
1031 Some(client_id),
1032 vec![case.ciphersuite()],
1033 None,
1034 Some(INITIAL_KEYING_MATERIAL_COUNT),
1035 )
1036 .unwrap();
1037 let cc: CoreCrypto = Session::try_new(cfg).await.unwrap().into();
1038 let context = cc.new_transaction().await.unwrap();
1039 assert!(context.proteus_init().await.is_ok());
1040 assert!(context.proteus_new_prekey(1).await.is_ok());
1041 context.finish().await.unwrap();
1042 #[cfg(not(target_family = "wasm"))]
1043 drop(db_file);
1044 }
1045
1046 #[apply(all_cred_cipher)]
1047 #[wasm_bindgen_test]
1048 async fn cc_can_2_phase_init(case: TestContext) {
1049 #[cfg(not(target_family = "wasm"))]
1050 let (path, db_file) = tmp_db_file();
1051 #[cfg(target_family = "wasm")]
1052 let (path, _) = tmp_db_file();
1053 let cfg = MlsClientConfiguration::try_new(
1055 path,
1056 DatabaseKey::generate(),
1057 None,
1058 vec![case.ciphersuite()],
1059 None,
1060 Some(INITIAL_KEYING_MATERIAL_COUNT),
1061 )
1062 .unwrap();
1063 let cc: CoreCrypto = Session::try_new(cfg).await.unwrap().into();
1064 let transaction = cc.new_transaction().await.unwrap();
1065 let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
1066 x509_test_chain.register_with_central(&transaction).await;
1067 assert!(transaction.proteus_init().await.is_ok());
1068 assert!(transaction.proteus_new_prekey(1).await.is_ok());
1070 let client_id = "alice";
1072 let identifier = match case.credential_type {
1073 MlsCredentialType::Basic => ClientIdentifier::Basic(client_id.into()),
1074 MlsCredentialType::X509 => {
1075 CertificateBundle::rand_identifier(client_id, &[x509_test_chain.find_local_intermediate_ca()])
1076 }
1077 };
1078 transaction
1079 .mls_init(
1080 identifier,
1081 vec![case.ciphersuite()],
1082 Some(INITIAL_KEYING_MATERIAL_COUNT),
1083 )
1084 .await
1085 .unwrap();
1086 assert_eq!(
1088 transaction
1089 .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, 2)
1090 .await
1091 .unwrap()
1092 .len(),
1093 2
1094 );
1095 #[cfg(not(target_family = "wasm"))]
1096 drop(db_file);
1097 }
1098
1099 #[async_std::test]
1100 #[wasm_bindgen_test]
1101 async fn can_init() {
1102 #[cfg(not(target_family = "wasm"))]
1103 let (path, db_file) = tmp_db_file();
1104 #[cfg(target_family = "wasm")]
1105 let (path, _) = tmp_db_file();
1106 let key = DatabaseKey::generate();
1107 let keystore = core_crypto_keystore::Connection::open_with_key(&path, &key)
1108 .await
1109 .unwrap();
1110 keystore.new_transaction().await.unwrap();
1111 let central = ProteusCentral::try_new(&keystore).await.unwrap();
1112 let identity = (*central.proteus_identity).clone();
1113 keystore.commit_transaction().await.unwrap();
1114
1115 let keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1116 .await
1117 .unwrap();
1118 keystore.new_transaction().await.unwrap();
1119 let central = ProteusCentral::try_new(&keystore).await.unwrap();
1120 keystore.commit_transaction().await.unwrap();
1121 assert_eq!(identity, *central.proteus_identity);
1122
1123 keystore.wipe().await.unwrap();
1124 #[cfg(not(target_family = "wasm"))]
1125 drop(db_file);
1126 }
1127
1128 #[async_std::test]
1129 #[wasm_bindgen_test]
1130 async fn can_talk_with_proteus() {
1131 #[cfg(not(target_family = "wasm"))]
1132 let (path, db_file) = tmp_db_file();
1133 #[cfg(target_family = "wasm")]
1134 let (path, _) = tmp_db_file();
1135
1136 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1137
1138 let key = DatabaseKey::generate();
1139 let mut keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1140 .await
1141 .unwrap();
1142 keystore.new_transaction().await.unwrap();
1143
1144 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
1145
1146 let mut bob = CryptoboxLike::init();
1147 let bob_pk_bundle = bob.new_prekey();
1148
1149 alice
1150 .session_from_prekey(&session_id, &bob_pk_bundle.serialise().unwrap())
1151 .await
1152 .unwrap();
1153
1154 let message = b"Hello world";
1155
1156 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
1157 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1158 assert_eq!(decrypted, message);
1159
1160 let encrypted = bob.encrypt(&session_id, message);
1161 let decrypted = alice.decrypt(&mut keystore, &session_id, &encrypted).await.unwrap();
1162 assert_eq!(decrypted, message);
1163
1164 keystore.commit_transaction().await.unwrap();
1165 keystore.wipe().await.unwrap();
1166 #[cfg(not(target_family = "wasm"))]
1167 drop(db_file);
1168 }
1169
1170 #[async_std::test]
1171 #[wasm_bindgen_test]
1172 async fn can_produce_proteus_consumed_prekeys() {
1173 #[cfg(not(target_family = "wasm"))]
1174 let (path, db_file) = tmp_db_file();
1175 #[cfg(target_family = "wasm")]
1176 let (path, _) = tmp_db_file();
1177
1178 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1179
1180 let key = DatabaseKey::generate();
1181 let mut keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1182 .await
1183 .unwrap();
1184 keystore.new_transaction().await.unwrap();
1185 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
1186
1187 let mut bob = CryptoboxLike::init();
1188
1189 let alice_prekey_bundle_ser = alice.new_prekey(1, &keystore).await.unwrap();
1190
1191 bob.init_session_from_prekey_bundle(&session_id, &alice_prekey_bundle_ser);
1192 let message = b"Hello world!";
1193 let encrypted = bob.encrypt(&session_id, message);
1194
1195 let (_, decrypted) = alice
1196 .session_from_message(&mut keystore, &session_id, &encrypted)
1197 .await
1198 .unwrap();
1199
1200 assert_eq!(message, decrypted.as_slice());
1201
1202 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
1203 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1204
1205 assert_eq!(message, decrypted.as_slice());
1206 keystore.commit_transaction().await.unwrap();
1207 keystore.wipe().await.unwrap();
1208 #[cfg(not(target_family = "wasm"))]
1209 drop(db_file);
1210 }
1211
1212 #[async_std::test]
1213 #[wasm_bindgen_test]
1214 async fn auto_prekeys_are_sequential() {
1215 use core_crypto_keystore::entities::ProteusPrekey;
1216 const GAP_AMOUNT: u16 = 5;
1217 const ID_TEST_RANGE: std::ops::RangeInclusive<u16> = 1..=30;
1218
1219 #[cfg(not(target_family = "wasm"))]
1220 let (path, db_file) = tmp_db_file();
1221 #[cfg(target_family = "wasm")]
1222 let (path, _) = tmp_db_file();
1223
1224 let key = DatabaseKey::generate();
1225 let keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1226 .await
1227 .unwrap();
1228 keystore.new_transaction().await.unwrap();
1229 let alice = ProteusCentral::try_new(&keystore).await.unwrap();
1230
1231 for i in ID_TEST_RANGE {
1232 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1233 assert_eq!(i, pk_id);
1234 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1235 assert_eq!(prekey.prekey_id.value(), pk_id);
1236 }
1237
1238 use rand::Rng as _;
1239 let mut rng = rand::thread_rng();
1240 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
1241 gap_ids.sort();
1242 gap_ids.dedup();
1243 while gap_ids.len() < GAP_AMOUNT as usize {
1244 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
1245 gap_ids.sort();
1246 gap_ids.dedup();
1247 }
1248 for gap_id in gap_ids.iter() {
1249 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
1250 }
1251
1252 gap_ids.sort();
1253
1254 for gap_id in gap_ids.iter() {
1255 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1256 assert_eq!(pk_id, *gap_id);
1257 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1258 assert_eq!(prekey.prekey_id.value(), *gap_id);
1259 }
1260
1261 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
1262 gap_ids.sort();
1263 gap_ids.dedup();
1264 while gap_ids.len() < GAP_AMOUNT as usize {
1265 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
1266 gap_ids.sort();
1267 gap_ids.dedup();
1268 }
1269 for gap_id in gap_ids.iter() {
1270 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
1271 }
1272
1273 let potential_range = *ID_TEST_RANGE.end()..=(*ID_TEST_RANGE.end() * 2);
1274 let potential_range_check = potential_range.clone();
1275 for _ in potential_range {
1276 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1277 assert!(gap_ids.contains(&pk_id) || potential_range_check.contains(&pk_id));
1278 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1279 assert_eq!(prekey.prekey_id.value(), pk_id);
1280 }
1281 keystore.commit_transaction().await.unwrap();
1282 keystore.wipe().await.unwrap();
1283 #[cfg(not(target_family = "wasm"))]
1284 drop(db_file);
1285 }
1286
1287 #[cfg(all(feature = "cryptobox-migrate", not(target_family = "wasm")))]
1288 #[async_std::test]
1289 async fn can_import_cryptobox() {
1290 use crate::CryptoboxMigrationErrorKind;
1291
1292 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1293
1294 let cryptobox_folder = tempfile::tempdir().unwrap();
1295 let alice = cryptobox::CBox::file_open(cryptobox_folder.path()).unwrap();
1296 let alice_fingerprint = alice.fingerprint();
1297
1298 let mut bob = CryptoboxLike::init();
1299 let bob_pk_bundle = bob.new_prekey();
1300
1301 let alice_pk_id = proteus::keys::PreKeyId::new(1u16);
1302 let alice_pk = alice.new_prekey(alice_pk_id).unwrap();
1303
1304 let mut alice_session = alice
1305 .session_from_prekey(session_id.clone(), &bob_pk_bundle.serialise().unwrap())
1306 .unwrap();
1307
1308 let message = b"Hello world!";
1309
1310 let alice_msg_envelope = alice_session.encrypt(message).unwrap();
1311 let decrypted = bob.decrypt(&session_id, &alice_msg_envelope).await;
1312 assert_eq!(decrypted, message);
1313
1314 alice.session_save(&mut alice_session).unwrap();
1315
1316 let encrypted = bob.encrypt(&session_id, &message[..]);
1317 let decrypted = alice_session.decrypt(&encrypted).unwrap();
1318 assert_eq!(decrypted, message);
1319
1320 alice.session_save(&mut alice_session).unwrap();
1321
1322 drop(alice);
1323
1324 let keystore_dir = tempfile::tempdir().unwrap();
1325 let keystore_file = keystore_dir.path().join("keystore");
1326
1327 let key = DatabaseKey::generate();
1328 let mut keystore =
1329 core_crypto_keystore::Connection::open_with_key(keystore_file.as_os_str().to_string_lossy(), &key)
1330 .await
1331 .unwrap();
1332 keystore.new_transaction().await.unwrap();
1333
1334 let Err(crate::Error::CryptoboxMigration(crate::CryptoboxMigrationError {
1335 source: CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(_),
1336 ..
1337 })) = ProteusCentral::cryptobox_migrate(&keystore, "invalid path").await
1338 else {
1339 panic!("ProteusCentral::cryptobox_migrate did not throw an error on invalid path");
1340 };
1341
1342 ProteusCentral::cryptobox_migrate(&keystore, &cryptobox_folder.path().to_string_lossy())
1343 .await
1344 .unwrap();
1345
1346 let mut proteus_central = ProteusCentral::try_new(&keystore).await.unwrap();
1347
1348 assert_eq!(proteus_central.fingerprint(), alice_fingerprint);
1350
1351 let alice_new_session_lock = proteus_central.session(&session_id, &keystore).await.unwrap().unwrap();
1353 let alice_new_session = alice_new_session_lock.read().await;
1354 assert_eq!(
1355 alice_new_session.session.local_identity().fingerprint(),
1356 alice_session.fingerprint_local()
1357 );
1358 assert_eq!(
1359 alice_new_session.session.remote_identity().fingerprint(),
1360 alice_session.fingerprint_remote()
1361 );
1362
1363 drop(alice_new_session);
1364 drop(alice_new_session_lock);
1365
1366 let keystore_pk = keystore.prekey(1).await.unwrap().unwrap();
1368 let keystore_pk = proteus_wasm::keys::PreKey::deserialise(&keystore_pk).unwrap();
1369 assert_eq!(alice_pk.prekey_id.value(), keystore_pk.key_id.value());
1370 assert_eq!(
1371 alice_pk.public_key.fingerprint(),
1372 keystore_pk.key_pair.public_key.fingerprint()
1373 );
1374
1375 let encrypted = proteus_central
1377 .encrypt(&mut keystore, &session_id, &message[..])
1378 .await
1379 .unwrap();
1380 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1381
1382 assert_eq!(&decrypted, &message[..]);
1383
1384 let encrypted = bob.encrypt(&session_id, &message[..]);
1389 let decrypted = proteus_central
1390 .decrypt(&mut keystore, &session_id, &encrypted)
1391 .await
1392 .unwrap();
1393 assert_eq!(&decrypted, &message[..]);
1394
1395 proteus_central.session_save(&keystore, &session_id).await.unwrap();
1396 keystore.commit_transaction().await.unwrap();
1397 keystore.wipe().await.unwrap();
1398 }
1399
1400 cfg_if::cfg_if! {
1401 if #[cfg(all(feature = "cryptobox-migrate", target_family = "wasm"))] {
1402 const CRYPTOBOX_JS_DBNAME: &str = "cryptobox-migrate-test";
1404 fn run_cryptobox(alice: CryptoboxLike) -> js_sys::Promise {
1408 wasm_bindgen_futures::future_to_promise(async move {
1409 use rexie::{Rexie, ObjectStore, TransactionMode};
1410 use wasm_bindgen::JsValue;
1411
1412 Rexie::builder(CRYPTOBOX_JS_DBNAME)
1414 .delete()
1415 .await.map_err(|err| err.to_string())?;
1416
1417 let rexie = Rexie::builder(CRYPTOBOX_JS_DBNAME)
1418 .version(1)
1419 .add_object_store(ObjectStore::new("keys").auto_increment(false))
1420 .add_object_store(ObjectStore::new("prekeys").auto_increment(false))
1421 .add_object_store(ObjectStore::new("sessions").auto_increment(false))
1422 .build()
1423 .await.map_err(|err| err.to_string())?;
1424
1425 let transaction = rexie.transaction(&["keys"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1427 let store = transaction.store("keys").map_err(|err| err.to_string())?;
1428
1429 use base64::Engine as _;
1430 let json = serde_json::json!({
1431 "created": 0,
1432 "id": "local_identity",
1433 "serialised": base64::prelude::BASE64_STANDARD.encode(alice.identity.serialise().unwrap()),
1434 "version": "1.0"
1435 });
1436 let js_value = serde_wasm_bindgen::to_value(&json)?;
1437
1438 store.add(&js_value, Some(&JsValue::from_str("local_identity"))).await.map_err(|err| err.to_string())?;
1439
1440 let transaction = rexie.transaction(&["prekeys"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1442 let store = transaction.store("prekeys").map_err(|err| err.to_string())?;
1443 for prekey in alice.prekeys.0.into_iter() {
1444 let id = prekey.key_id.value().to_string();
1445 let json = serde_json::json!({
1446 "created": 0,
1447 "id": &id,
1448 "serialised": base64::prelude::BASE64_STANDARD.encode(prekey.serialise().unwrap()),
1449 "version": "1.0"
1450 });
1451 let js_value = serde_wasm_bindgen::to_value(&json)?;
1452 store.add(&js_value, Some(&JsValue::from_str(&id))).await.map_err(|err| err.to_string())?;
1453 }
1454
1455 let transaction = rexie.transaction(&["sessions"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1457 let store = transaction.store("sessions").map_err(|err| err.to_string())?;
1458 for (session_id, session) in alice.sessions.into_iter() {
1459 let json = serde_json::json!({
1460 "created": 0,
1461 "id": session_id,
1462 "serialised": base64::prelude::BASE64_STANDARD.encode(session.serialise().unwrap()),
1463 "version": "1.0"
1464 });
1465
1466 let js_value = serde_wasm_bindgen::to_value(&json)?;
1467 store.add(&js_value, Some(&JsValue::from_str(&session_id))).await.map_err(|err| err.to_string())?;
1468 }
1469
1470 Ok(JsValue::UNDEFINED)
1471 })
1472 }
1473
1474 #[wasm_bindgen_test]
1475 async fn can_import_cryptobox() {
1476 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1477
1478 let mut alice = CryptoboxLike::init();
1479 let alice_fingerprint = alice.fingerprint();
1480 const PREKEY_COUNT: usize = 10;
1481 let prekey_iter_range = 0..PREKEY_COUNT;
1482 let prekey_bundles: Vec<proteus_wasm::keys::PreKeyBundle> = prekey_iter_range.clone().map(|_| alice.new_prekey()).collect();
1484
1485 let mut bob = CryptoboxLike::init();
1487 let bob_pk_bundle = bob.new_prekey();
1488 let message = b"Hello world!";
1489
1490 alice.init_session_from_prekey_bundle(&session_id, &bob_pk_bundle.serialise().unwrap());
1491 let alice_to_bob_message = alice.encrypt(&session_id, message);
1492 let decrypted = bob.decrypt(&session_id, &alice_to_bob_message).await;
1493 assert_eq!(&message[..], decrypted.as_slice());
1494
1495 let bob_to_alice_message = bob.encrypt(&session_id, message);
1496 let decrypted = alice.decrypt(&session_id, &bob_to_alice_message).await;
1497 assert_eq!(&message[..], decrypted.as_slice());
1498
1499 let alice_session = alice.session(&session_id);
1500 let alice_session_fingerprint_local = alice_session.local_identity().fingerprint();
1501 let alice_session_fingerprint_remote = alice_session.remote_identity().fingerprint();
1502
1503 let _ = wasm_bindgen_futures::JsFuture::from(run_cryptobox(alice)).await.unwrap();
1504
1505 use sha2::Digest as _;
1506 let old_key = "test";
1507 let new_key = DatabaseKey::try_from(sha2::Sha256::digest(old_key).as_slice()).unwrap();
1508
1509 let name = format!("{CRYPTOBOX_JS_DBNAME}-imported");
1510 let _ = core_crypto_keystore::connection::platform::open_and_migrate_pre_v4(&name, old_key).await;
1511
1512 core_crypto_keystore::Connection::migrate_db_key_type_to_bytes(&name, old_key, &new_key).await.unwrap();
1513
1514 let mut keystore = core_crypto_keystore::Connection::open_with_key(&name, &new_key).await.unwrap();
1515 keystore.new_transaction().await.unwrap();
1516 let Err(crate::Error::CryptoboxMigration(crate::CryptoboxMigrationError{
1517 source: crate::CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(_),
1518 ..
1519 })) = ProteusCentral::cryptobox_migrate(&keystore, "invalid path").await else {
1520 panic!("ProteusCentral::cryptobox_migrate did not throw an error on invalid path");
1521 };
1522
1523 ProteusCentral::cryptobox_migrate(&keystore, CRYPTOBOX_JS_DBNAME).await.unwrap();
1524
1525 let mut proteus_central = ProteusCentral::try_new(&keystore).await.unwrap();
1526
1527 assert_eq!(proteus_central.fingerprint(), alice_fingerprint);
1529
1530 let alice_new_session_lock = proteus_central
1532 .session(&session_id, &keystore)
1533 .await
1534 .unwrap()
1535 .unwrap();
1536 let alice_new_session = alice_new_session_lock.read().await;
1537 assert_eq!(
1538 alice_new_session.session.local_identity().fingerprint(),
1539 alice_session_fingerprint_local
1540 );
1541 assert_eq!(
1542 alice_new_session.session.remote_identity().fingerprint(),
1543 alice_session_fingerprint_remote
1544 );
1545
1546 drop(alice_new_session);
1547 drop(alice_new_session_lock);
1548
1549 for i in prekey_iter_range {
1551 let prekey_id = (i + 1) as u16;
1552 let keystore_pk = keystore.prekey(prekey_id).await.unwrap().unwrap();
1553 let keystore_pk = proteus_wasm::keys::PreKey::deserialise(&keystore_pk).unwrap();
1554 let alice_pk = &prekey_bundles[i];
1555
1556 assert_eq!(alice_pk.prekey_id.value(), keystore_pk.key_id.value());
1557 assert_eq!(
1558 alice_pk.public_key.fingerprint(),
1559 keystore_pk.key_pair.public_key.fingerprint()
1560 );
1561 }
1562
1563
1564 let encrypted = proteus_central.encrypt(&mut keystore, &session_id, &message[..]).await.unwrap();
1566 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1567
1568 assert_eq!(&decrypted, &message[..]);
1569
1570 let encrypted = bob.encrypt(&session_id, &message[..]);
1572 let decrypted = proteus_central
1573 .decrypt(&mut keystore, &session_id, &encrypted)
1574 .await
1575 .unwrap();
1576 assert_eq!(&decrypted, &message[..]);
1577 keystore.commit_transaction().await.unwrap();
1578
1579 keystore.wipe().await.unwrap();
1580 }
1581 }
1582 }
1583}