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