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