1use crate::{
2 CoreCrypto, Error, KeystoreError, LeafError, ProteusError, RecursiveError, Result,
3 group_store::{GroupStore, GroupStoreEntity, GroupStoreValue},
4 transaction_context::TransactionContext,
5};
6use core_crypto_keystore::{
7 Connection as CryptoKeystore,
8 connection::FetchFromDatabase,
9 entities::{ProteusIdentity, ProteusSession},
10};
11use proteus_wasm::{
12 keys::{IdentityKeyPair, PreKeyBundle},
13 message::Envelope,
14 session::Session,
15};
16use std::{collections::HashMap, sync::Arc};
17
18pub type SessionIdentifier = String;
20
21#[derive(Debug)]
23pub struct ProteusConversationSession {
24 pub(crate) identifier: SessionIdentifier,
25 pub(crate) session: Session<Arc<IdentityKeyPair>>,
26}
27
28impl ProteusConversationSession {
29 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
31 self.session
32 .encrypt(plaintext)
33 .and_then(|e| e.serialise())
34 .map_err(ProteusError::wrap("encrypting message for proteus session"))
35 .map_err(Into::into)
36 }
37
38 pub async fn decrypt(
40 &mut self,
41 store: &mut core_crypto_keystore::Connection,
42 ciphertext: &[u8],
43 ) -> Result<Vec<u8>> {
44 let envelope = Envelope::deserialise(ciphertext).map_err(ProteusError::wrap("deserializing envelope"))?;
45 self.session
46 .decrypt(store, &envelope)
47 .await
48 .map_err(ProteusError::wrap("decrypting message for proteus session"))
49 .map_err(Into::into)
50 }
51
52 pub fn identifier(&self) -> &str {
54 &self.identifier
55 }
56
57 pub fn fingerprint_local(&self) -> String {
59 self.session.local_identity().fingerprint()
60 }
61
62 pub fn fingerprint_remote(&self) -> String {
64 self.session.remote_identity().fingerprint()
65 }
66}
67
68#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
69#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
70impl GroupStoreEntity for ProteusConversationSession {
71 type RawStoreValue = core_crypto_keystore::entities::ProteusSession;
72 type IdentityType = Arc<proteus_wasm::keys::IdentityKeyPair>;
73
74 #[cfg(test)]
75 fn id(&self) -> &[u8] {
76 unreachable!()
77 }
78
79 async fn fetch_from_id(
80 id: &[u8],
81 identity: Option<Self::IdentityType>,
82 keystore: &impl FetchFromDatabase,
83 ) -> crate::Result<Option<Self>> {
84 let result = keystore
85 .find::<Self::RawStoreValue>(id)
86 .await
87 .map_err(KeystoreError::wrap("finding raw group store entity by id"))?;
88 let Some(store_value) = result else {
89 return Ok(None);
90 };
91
92 let Some(identity) = identity else {
93 return Err(crate::Error::ProteusNotInitialized);
94 };
95
96 let session = proteus_wasm::session::Session::deserialise(identity, &store_value.session)
97 .map_err(ProteusError::wrap("deserializing session"))?;
98
99 Ok(Some(Self {
100 identifier: store_value.id.clone(),
101 session,
102 }))
103 }
104
105 #[cfg(test)]
106 async fn fetch_all(_keystore: &impl FetchFromDatabase) -> Result<Vec<Self>>
107 where
108 Self: Sized,
109 {
110 unreachable!()
111 }
112}
113
114impl CoreCrypto {
115 pub async fn proteus_session(
119 &self,
120 session_id: &str,
121 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
122 let mut mutex = self.proteus.lock().await;
123 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
124 let keystore = self.mls.crypto_provider.keystore();
125 proteus.session(session_id, &keystore).await
126 }
127
128 pub async fn proteus_session_exists(&self, session_id: &str) -> Result<bool> {
132 let mut mutex = self.proteus.lock().await;
133 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
134 let keystore = self.mls.crypto_provider.keystore();
135 Ok(proteus.session_exists(session_id, &keystore).await)
136 }
137
138 pub fn proteus_last_resort_prekey_id() -> u16 {
140 ProteusCentral::last_resort_prekey_id()
141 }
142
143 pub async fn proteus_fingerprint(&self) -> Result<String> {
147 let mutex = self.proteus.lock().await;
148 let proteus = mutex.as_ref().ok_or(Error::ProteusNotInitialized)?;
149 Ok(proteus.fingerprint())
150 }
151
152 pub async fn proteus_fingerprint_local(&self, session_id: &str) -> Result<String> {
156 let mut mutex = self.proteus.lock().await;
157 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
158 let keystore = self.mls.crypto_provider.keystore();
159 proteus.fingerprint_local(session_id, &keystore).await
160 }
161
162 pub async fn proteus_fingerprint_remote(&self, session_id: &str) -> Result<String> {
166 let mut mutex = self.proteus.lock().await;
167 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
168 let keystore = self.mls.crypto_provider.keystore();
169 proteus.fingerprint_remote(session_id, &keystore).await
170 }
171}
172
173impl TransactionContext {
174 pub async fn proteus_init(&self) -> Result<()> {
176 let keystore = self
177 .keystore()
178 .await
179 .map_err(RecursiveError::transaction("getting keystore"))?;
180 let proteus_client = ProteusCentral::try_new(&keystore).await?;
181
182 let _ = proteus_client.last_resort_prekey(&keystore).await?;
184
185 let mutex = self
186 .proteus_central()
187 .await
188 .map_err(RecursiveError::transaction("getting proteus client"))?;
189 let mut guard = mutex.lock().await;
190 *guard = Some(proteus_client);
191 Ok(())
192 }
193
194 pub async fn proteus_reload_sessions(&self) -> Result<()> {
198 let arc = self
199 .proteus_central()
200 .await
201 .map_err(RecursiveError::transaction("getting proteus client"))?;
202 let mut mutex = arc.lock().await;
203 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
204 let keystore = self
205 .keystore()
206 .await
207 .map_err(RecursiveError::transaction("getting keystore"))?;
208 proteus.reload_sessions(&keystore).await
209 }
210
211 pub async fn proteus_session_from_prekey(
215 &self,
216 session_id: &str,
217 prekey: &[u8],
218 ) -> Result<GroupStoreValue<ProteusConversationSession>> {
219 let arc = self
220 .proteus_central()
221 .await
222 .map_err(RecursiveError::transaction("getting proteus client"))?;
223 let mut mutex = arc.lock().await;
224 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
225 let keystore = self
226 .keystore()
227 .await
228 .map_err(RecursiveError::transaction("getting keystore"))?;
229 let session = proteus.session_from_prekey(session_id, prekey).await?;
230 ProteusCentral::session_save_by_ref(&keystore, session.clone()).await?;
231
232 Ok(session)
233 }
234
235 pub async fn proteus_session_from_message(
239 &self,
240 session_id: &str,
241 envelope: &[u8],
242 ) -> Result<(GroupStoreValue<ProteusConversationSession>, Vec<u8>)> {
243 let arc = self
244 .proteus_central()
245 .await
246 .map_err(RecursiveError::transaction("getting proteus client"))?;
247 let mut mutex = arc.lock().await;
248 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
249 let mut keystore = self
250 .keystore()
251 .await
252 .map_err(RecursiveError::transaction("getting keystore"))?;
253 let (session, message) = proteus
254 .session_from_message(&mut keystore, session_id, envelope)
255 .await?;
256 ProteusCentral::session_save_by_ref(&keystore, session.clone()).await?;
257
258 Ok((session, message))
259 }
260
261 pub async fn proteus_session_save(&self, session_id: &str) -> Result<()> {
265 let arc = self
266 .proteus_central()
267 .await
268 .map_err(RecursiveError::transaction("getting proteus client"))?;
269 let mut mutex = arc.lock().await;
270 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
271 let keystore = self
272 .keystore()
273 .await
274 .map_err(RecursiveError::transaction("getting keystore"))?;
275 proteus.session_save(&keystore, session_id).await
276 }
277
278 pub async fn proteus_session_delete(&self, session_id: &str) -> Result<()> {
282 let arc = self
283 .proteus_central()
284 .await
285 .map_err(RecursiveError::transaction("getting proteus client"))?;
286 let mut mutex = arc.lock().await;
287 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
288 let keystore = self
289 .keystore()
290 .await
291 .map_err(RecursiveError::transaction("getting keystore"))?;
292 proteus.session_delete(&keystore, session_id).await
293 }
294
295 pub async fn proteus_session(
299 &self,
300 session_id: &str,
301 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
302 let arc = self
303 .proteus_central()
304 .await
305 .map_err(RecursiveError::transaction("getting proteus client"))?;
306 let mut mutex = arc.lock().await;
307 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
308 let keystore = self
309 .keystore()
310 .await
311 .map_err(RecursiveError::transaction("getting keystore"))?;
312 proteus.session(session_id, &keystore).await
313 }
314
315 pub async fn proteus_session_exists(&self, session_id: &str) -> Result<bool> {
319 let arc = self
320 .proteus_central()
321 .await
322 .map_err(RecursiveError::transaction("getting proteus client"))?;
323 let mut mutex = arc.lock().await;
324 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
325 let keystore = self
326 .keystore()
327 .await
328 .map_err(RecursiveError::transaction("getting keystore"))?;
329 Ok(proteus.session_exists(session_id, &keystore).await)
330 }
331
332 pub async fn proteus_decrypt(&self, session_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
336 let arc = self
337 .proteus_central()
338 .await
339 .map_err(RecursiveError::transaction("getting proteus client"))?;
340 let mut mutex = arc.lock().await;
341 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
342 let mut keystore = self
343 .keystore()
344 .await
345 .map_err(RecursiveError::transaction("getting keystore"))?;
346 proteus.decrypt(&mut keystore, session_id, ciphertext).await
347 }
348
349 pub async fn proteus_encrypt(&self, session_id: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
353 let arc = self
354 .proteus_central()
355 .await
356 .map_err(RecursiveError::transaction("getting proteus client"))?;
357 let mut mutex = arc.lock().await;
358 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
359 let mut keystore = self
360 .keystore()
361 .await
362 .map_err(RecursiveError::transaction("getting keystore"))?;
363 proteus.encrypt(&mut keystore, session_id, plaintext).await
364 }
365
366 pub async fn proteus_encrypt_batched(
371 &self,
372 sessions: &[impl AsRef<str>],
373 plaintext: &[u8],
374 ) -> Result<std::collections::HashMap<String, Vec<u8>>> {
375 let arc = self
376 .proteus_central()
377 .await
378 .map_err(RecursiveError::transaction("getting proteus client"))?;
379 let mut mutex = arc.lock().await;
380 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
381 let mut keystore = self
382 .keystore()
383 .await
384 .map_err(RecursiveError::transaction("getting keystore"))?;
385 proteus.encrypt_batched(&mut keystore, sessions, plaintext).await
386 }
387
388 pub async fn proteus_new_prekey(&self, prekey_id: u16) -> Result<Vec<u8>> {
392 let arc = self
393 .proteus_central()
394 .await
395 .map_err(RecursiveError::transaction("getting proteus client"))?;
396 let mut mutex = arc.lock().await;
397 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
398 let keystore = self
399 .keystore()
400 .await
401 .map_err(RecursiveError::transaction("getting keystore"))?;
402 proteus.new_prekey(prekey_id, &keystore).await
403 }
404
405 pub async fn proteus_new_prekey_auto(&self) -> Result<(u16, Vec<u8>)> {
409 let arc = self
410 .proteus_central()
411 .await
412 .map_err(RecursiveError::transaction("getting proteus client"))?;
413 let mut mutex = arc.lock().await;
414 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
415 let keystore = self
416 .keystore()
417 .await
418 .map_err(RecursiveError::transaction("getting keystore"))?;
419 proteus.new_prekey_auto(&keystore).await
420 }
421
422 pub async fn proteus_last_resort_prekey(&self) -> Result<Vec<u8>> {
424 let arc = self
425 .proteus_central()
426 .await
427 .map_err(RecursiveError::transaction("getting proteus client"))?;
428 let mut mutex = arc.lock().await;
429 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
430 let keystore = self
431 .keystore()
432 .await
433 .map_err(RecursiveError::transaction("getting keystore"))?;
434
435 proteus.last_resort_prekey(&keystore).await
436 }
437
438 pub fn proteus_last_resort_prekey_id() -> u16 {
440 ProteusCentral::last_resort_prekey_id()
441 }
442
443 pub async fn proteus_fingerprint(&self) -> Result<String> {
447 let arc = self
448 .proteus_central()
449 .await
450 .map_err(RecursiveError::transaction("getting proteus client"))?;
451 let mut mutex = arc.lock().await;
452 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
453 Ok(proteus.fingerprint())
454 }
455
456 pub async fn proteus_fingerprint_local(&self, session_id: &str) -> Result<String> {
460 let arc = self
461 .proteus_central()
462 .await
463 .map_err(RecursiveError::transaction("getting proteus client"))?;
464 let mut mutex = arc.lock().await;
465 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
466 let keystore = self
467 .keystore()
468 .await
469 .map_err(RecursiveError::transaction("getting keystore"))?;
470 proteus.fingerprint_local(session_id, &keystore).await
471 }
472
473 pub async fn proteus_fingerprint_remote(&self, session_id: &str) -> Result<String> {
477 let arc = self
478 .proteus_central()
479 .await
480 .map_err(RecursiveError::transaction("getting proteus client"))?;
481 let mut mutex = arc.lock().await;
482 let proteus = mutex.as_mut().ok_or(Error::ProteusNotInitialized)?;
483 let keystore = self
484 .keystore()
485 .await
486 .map_err(RecursiveError::transaction("getting keystore"))?;
487 proteus.fingerprint_remote(session_id, &keystore).await
488 }
489
490 pub async fn proteus_cryptobox_migrate(&self, path: &str) -> Result<()> {
494 let keystore = self
495 .keystore()
496 .await
497 .map_err(RecursiveError::transaction("getting keystore"))?;
498 ProteusCentral::cryptobox_migrate(&keystore, path).await
499 }
500}
501
502#[derive(Debug)]
507pub struct ProteusCentral {
508 proteus_identity: Arc<IdentityKeyPair>,
509 proteus_sessions: GroupStore<ProteusConversationSession>,
510}
511
512impl ProteusCentral {
513 pub async fn try_new(keystore: &CryptoKeystore) -> Result<Self> {
515 let proteus_identity: Arc<IdentityKeyPair> = Arc::new(Self::load_or_create_identity(keystore).await?);
516 let proteus_sessions = Self::restore_sessions(keystore, &proteus_identity).await?;
517
518 Ok(Self {
519 proteus_identity,
520 proteus_sessions,
521 })
522 }
523
524 pub(crate) async fn reload_sessions(&mut self, keystore: &CryptoKeystore) -> Result<()> {
526 self.proteus_sessions = Self::restore_sessions(keystore, &self.proteus_identity).await?;
527 Ok(())
528 }
529
530 async fn load_or_create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
533 let Some(identity) = keystore
534 .find::<ProteusIdentity>(&[])
535 .await
536 .map_err(KeystoreError::wrap("finding proteus identity"))?
537 else {
538 return Self::create_identity(keystore).await;
539 };
540
541 let sk = identity.sk_raw();
542 let pk = identity.pk_raw();
543
544 IdentityKeyPair::from_raw_key_pair(*sk, *pk)
546 .map_err(ProteusError::wrap("constructing identity keypair"))
547 .map_err(Into::into)
548 }
549
550 async fn create_identity(keystore: &CryptoKeystore) -> Result<IdentityKeyPair> {
552 let kp = IdentityKeyPair::new();
553 let pk = kp.public_key.public_key.as_slice().to_vec();
554
555 let ks_identity = ProteusIdentity {
556 sk: kp.secret_key.to_keypair_bytes().into(),
557 pk,
558 };
559 keystore
560 .save(ks_identity)
561 .await
562 .map_err(KeystoreError::wrap("saving new proteus identity"))?;
563
564 Ok(kp)
565 }
566
567 async fn restore_sessions(
569 keystore: &core_crypto_keystore::Connection,
570 identity: &Arc<IdentityKeyPair>,
571 ) -> Result<GroupStore<ProteusConversationSession>> {
572 let mut proteus_sessions = GroupStore::new_with_limit(crate::group_store::ITEM_LIMIT * 2);
573 for session in keystore
574 .find_all::<ProteusSession>(Default::default())
575 .await
576 .map_err(KeystoreError::wrap("finding all proteus sessions"))?
577 .into_iter()
578 {
579 let proteus_session = Session::deserialise(identity.clone(), &session.session)
580 .map_err(ProteusError::wrap("deserializing session"))?;
581
582 let identifier = session.id.clone();
583
584 let proteus_conversation = ProteusConversationSession {
585 identifier: identifier.clone(),
586 session: proteus_session,
587 };
588
589 if proteus_sessions
590 .try_insert(identifier.into_bytes(), proteus_conversation)
591 .is_err()
592 {
593 break;
594 }
595 }
596
597 Ok(proteus_sessions)
598 }
599
600 pub async fn session_from_prekey(
602 &mut self,
603 session_id: &str,
604 key: &[u8],
605 ) -> Result<GroupStoreValue<ProteusConversationSession>> {
606 let prekey = PreKeyBundle::deserialise(key).map_err(ProteusError::wrap("deserializing prekey bundle"))?;
607 let proteus_session = Session::init_from_prekey::<core_crypto_keystore::CryptoKeystoreError>(
639 self.proteus_identity.clone(),
640 prekey,
641 )
642 .map_err(ProteusError::wrap("initializing session from prekey"))?;
643
644 let proteus_conversation = ProteusConversationSession {
645 identifier: session_id.into(),
646 session: proteus_session,
647 };
648
649 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
650
651 Ok(self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone())
652 }
653
654 pub(crate) async fn session_from_message(
656 &mut self,
657 keystore: &mut CryptoKeystore,
658 session_id: &str,
659 envelope: &[u8],
660 ) -> Result<(GroupStoreValue<ProteusConversationSession>, Vec<u8>)> {
661 let message = Envelope::deserialise(envelope).map_err(ProteusError::wrap("deserialising envelope"))?;
662 let (session, payload) = Session::init_from_message(self.proteus_identity.clone(), keystore, &message)
663 .await
664 .map_err(ProteusError::wrap("initializing session from message"))?;
665
666 let proteus_conversation = ProteusConversationSession {
667 identifier: session_id.into(),
668 session,
669 };
670
671 self.proteus_sessions.insert(session_id.into(), proteus_conversation);
672
673 Ok((
674 self.proteus_sessions.get(session_id.as_bytes()).unwrap().clone(),
675 payload,
676 ))
677 }
678
679 pub(crate) async fn session_save(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
683 if let Some(session) = self
684 .proteus_sessions
685 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
686 .await?
687 {
688 Self::session_save_by_ref(keystore, session).await?;
689 }
690
691 Ok(())
692 }
693
694 async fn session_save_by_ref(
695 keystore: &CryptoKeystore,
696 session: GroupStoreValue<ProteusConversationSession>,
697 ) -> Result<()> {
698 let session = session.read().await;
699 let db_session = ProteusSession {
700 id: session.identifier().to_string(),
701 session: session
702 .session
703 .serialise()
704 .map_err(ProteusError::wrap("serializing session"))?,
705 };
706 keystore
707 .save(db_session)
708 .await
709 .map_err(KeystoreError::wrap("saving proteus session"))?;
710 Ok(())
711 }
712
713 pub(crate) async fn session_delete(&mut self, keystore: &CryptoKeystore, session_id: &str) -> Result<()> {
715 if keystore.remove::<ProteusSession, _>(session_id).await.is_ok() {
716 let _ = self.proteus_sessions.remove(session_id.as_bytes());
717 }
718 Ok(())
719 }
720
721 pub(crate) async fn session(
723 &mut self,
724 session_id: &str,
725 keystore: &CryptoKeystore,
726 ) -> Result<Option<GroupStoreValue<ProteusConversationSession>>> {
727 self.proteus_sessions
728 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
729 .await
730 }
731
732 pub(crate) async fn session_exists(&mut self, session_id: &str, keystore: &CryptoKeystore) -> bool {
734 self.session(session_id, keystore).await.ok().flatten().is_some()
735 }
736
737 pub(crate) async fn decrypt(
740 &mut self,
741 keystore: &mut CryptoKeystore,
742 session_id: &str,
743 ciphertext: &[u8],
744 ) -> Result<Vec<u8>> {
745 let session = self
746 .proteus_sessions
747 .get_fetch(session_id.as_bytes(), keystore, Some(self.proteus_identity.clone()))
748 .await?
749 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
750 .map_err(ProteusError::wrap("getting session"))?;
751
752 let plaintext = session.write().await.decrypt(keystore, ciphertext).await?;
753 ProteusCentral::session_save_by_ref(keystore, session).await?;
754
755 Ok(plaintext)
756 }
757
758 pub(crate) async fn encrypt(
760 &mut self,
761 keystore: &mut CryptoKeystore,
762 session_id: &str,
763 plaintext: &[u8],
764 ) -> Result<Vec<u8>> {
765 let session = self
766 .session(session_id, keystore)
767 .await?
768 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
769 .map_err(ProteusError::wrap("getting session"))?;
770
771 let ciphertext = session.write().await.encrypt(plaintext)?;
772 ProteusCentral::session_save_by_ref(keystore, session).await?;
773
774 Ok(ciphertext)
775 }
776
777 pub(crate) async fn encrypt_batched(
780 &mut self,
781 keystore: &mut CryptoKeystore,
782 sessions: &[impl AsRef<str>],
783 plaintext: &[u8],
784 ) -> Result<HashMap<String, Vec<u8>>> {
785 let mut acc = HashMap::new();
786 for session_id in sessions {
787 if let Some(session) = self.session(session_id.as_ref(), keystore).await? {
788 let mut session_w = session.write().await;
789 acc.insert(session_w.identifier.clone(), session_w.encrypt(plaintext)?);
790 drop(session_w);
791
792 ProteusCentral::session_save_by_ref(keystore, session).await?;
793 }
794 }
795 Ok(acc)
796 }
797
798 pub(crate) async fn new_prekey(&self, id: u16, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
800 use proteus_wasm::keys::{PreKey, PreKeyId};
801
802 let prekey_id = PreKeyId::new(id);
803 let prekey = PreKey::new(prekey_id);
804 let keystore_prekey = core_crypto_keystore::entities::ProteusPrekey::from_raw(
805 id,
806 prekey.serialise().map_err(ProteusError::wrap("serialising prekey"))?,
807 );
808 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &prekey);
809 let bundle = bundle
810 .serialise()
811 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
812 keystore
813 .save(keystore_prekey)
814 .await
815 .map_err(KeystoreError::wrap("saving keystore prekey"))?;
816 Ok(bundle)
817 }
818
819 pub(crate) async fn new_prekey_auto(&self, keystore: &CryptoKeystore) -> Result<(u16, Vec<u8>)> {
823 let id = core_crypto_keystore::entities::ProteusPrekey::get_free_id(keystore)
824 .await
825 .map_err(KeystoreError::wrap("getting proteus prekey by id"))?;
826 Ok((id, self.new_prekey(id, keystore).await?))
827 }
828
829 pub fn last_resort_prekey_id() -> u16 {
831 proteus_wasm::keys::MAX_PREKEY_ID.value()
832 }
833
834 pub(crate) async fn last_resort_prekey(&self, keystore: &CryptoKeystore) -> Result<Vec<u8>> {
837 let last_resort = if let Some(last_resort) = keystore
838 .find::<core_crypto_keystore::entities::ProteusPrekey>(
839 Self::last_resort_prekey_id().to_le_bytes().as_slice(),
840 )
841 .await
842 .map_err(KeystoreError::wrap("finding proteus prekey"))?
843 {
844 proteus_wasm::keys::PreKey::deserialise(&last_resort.prekey)
845 .map_err(ProteusError::wrap("deserialising proteus prekey"))?
846 } else {
847 let last_resort = proteus_wasm::keys::PreKey::last_resort();
848
849 use core_crypto_keystore::CryptoKeystoreProteus as _;
850 keystore
851 .proteus_store_prekey(
852 Self::last_resort_prekey_id(),
853 &last_resort
854 .serialise()
855 .map_err(ProteusError::wrap("serialising last resort prekey"))?,
856 )
857 .await
858 .map_err(KeystoreError::wrap("storing proteus prekey"))?;
859
860 last_resort
861 };
862
863 let bundle = PreKeyBundle::new(self.proteus_identity.as_ref().public_key.clone(), &last_resort);
864 let bundle = bundle
865 .serialise()
866 .map_err(ProteusError::wrap("serialising prekey bundle"))?;
867
868 Ok(bundle)
869 }
870
871 pub fn identity(&self) -> &IdentityKeyPair {
873 self.proteus_identity.as_ref()
874 }
875
876 pub fn fingerprint(&self) -> String {
878 self.proteus_identity.as_ref().public_key.fingerprint()
879 }
880
881 pub(crate) async fn fingerprint_local(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
886 let session = self
887 .session(session_id, keystore)
888 .await?
889 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
890 .map_err(ProteusError::wrap("getting session"))?;
891 let fingerprint = session.read().await.fingerprint_local();
892 Ok(fingerprint)
893 }
894
895 pub(crate) async fn fingerprint_remote(&mut self, session_id: &str, keystore: &CryptoKeystore) -> Result<String> {
900 let session = self
901 .session(session_id, keystore)
902 .await?
903 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
904 .map_err(ProteusError::wrap("getting session"))?;
905 let fingerprint = session.read().await.fingerprint_remote();
906 Ok(fingerprint)
907 }
908
909 pub fn fingerprint_prekeybundle(prekey: &[u8]) -> Result<String> {
914 let prekey = PreKeyBundle::deserialise(prekey).map_err(ProteusError::wrap("deserialising prekey bundle"))?;
915 Ok(prekey.identity_key.fingerprint())
916 }
917
918 #[cfg_attr(not(feature = "cryptobox-migrate"), allow(unused_variables))]
920 pub(crate) async fn cryptobox_migrate(keystore: &CryptoKeystore, path: &str) -> Result<()> {
921 cfg_if::cfg_if! {
922 if #[cfg(feature = "cryptobox-migrate")] {
923 Self::cryptobox_migrate_impl(keystore, path).await?;
924 Ok(())
925 } else {
926 Err(Error::FeatureDisabled("cryptobox-migrate"))
927 }
928 }
929 }
930}
931
932#[cfg(feature = "cryptobox-migrate")]
933#[allow(dead_code)]
934impl ProteusCentral {
935 #[cfg(not(target_family = "wasm"))]
936 async fn cryptobox_migrate_impl(keystore: &CryptoKeystore, path: &str) -> Result<()> {
937 let root_dir = std::path::PathBuf::from(path);
938
939 if !root_dir.exists() {
940 return Err(CryptoboxMigrationError::wrap("root dir does not exist")(
941 crate::CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(path.into()),
942 )
943 .into());
944 }
945
946 let session_dir = root_dir.join("sessions");
947 let prekey_dir = root_dir.join("prekeys");
948
949 let missing_identity = Err(CryptoboxMigrationError::wrap("taking identity keypair")(
951 crate::CryptoboxMigrationErrorKind::IdentityNotFound(path.into()),
952 )
953 .into());
954
955 let identity = if let Some(store_kp) = keystore
956 .find::<ProteusIdentity>(&[])
957 .await
958 .map_err(KeystoreError::wrap("finding proteus identity"))?
959 {
960 Box::new(
961 IdentityKeyPair::from_raw_key_pair(*store_kp.sk_raw(), *store_kp.pk_raw())
962 .map_err(ProteusError::wrap("constructing identity keypair from raw keypair"))?,
963 )
964 } else {
965 let identity_dir = root_dir.join("identities");
966
967 let identity = identity_dir.join("local");
968 let legacy_identity = identity_dir.join("local_identity");
969 let kp = if legacy_identity.exists() {
971 let kp_cbor = async_fs::read(&legacy_identity)
972 .await
973 .map_err(CryptoboxMigrationError::wrap("reading legacy identity from filesystem"))?;
974 let kp = IdentityKeyPair::deserialise(&kp_cbor)
975 .map_err(ProteusError::wrap("deserialising identity keypair"))?;
976
977 Box::new(kp)
978 } else if identity.exists() {
979 let kp_cbor = async_fs::read(&identity)
980 .await
981 .map_err(CryptoboxMigrationError::wrap("reading identity from filesystem"))?;
982 let kp = proteus_wasm::identity::Identity::deserialise(&kp_cbor)
983 .map_err(ProteusError::wrap("deserialising identity"))?;
984
985 if let proteus_wasm::identity::Identity::Sec(kp) = kp {
986 kp.into_owned()
987 } else {
988 return missing_identity;
989 }
990 } else {
991 return missing_identity;
992 };
993
994 let pk = kp.public_key.public_key.as_slice().into();
995
996 let ks_identity = ProteusIdentity {
997 sk: kp.secret_key.to_keypair_bytes().into(),
998 pk,
999 };
1000
1001 keystore
1002 .save(ks_identity)
1003 .await
1004 .map_err(KeystoreError::wrap("saving proteus identity"))?;
1005
1006 if legacy_identity.exists() {
1007 async_fs::remove_file(legacy_identity)
1008 .await
1009 .map_err(CryptoboxMigrationError::wrap("removing legacy identity"))?;
1010 }
1011
1012 kp
1013 };
1014
1015 let identity = *identity;
1016
1017 use futures_lite::stream::StreamExt as _;
1018 let mut session_entries = async_fs::read_dir(session_dir)
1020 .await
1021 .map_err(CryptoboxMigrationError::wrap("reading session entries"))?;
1022 while let Some(session_file) = session_entries
1023 .try_next()
1024 .await
1025 .map_err(CryptoboxMigrationError::wrap("getting next session file"))?
1026 {
1027 let proteus_session_id: String = session_file.file_name().to_string_lossy().to_string();
1029
1030 if keystore
1032 .find::<ProteusSession>(proteus_session_id.as_bytes())
1033 .await
1034 .map_err(KeystoreError::wrap("finding proteus session by id"))?
1035 .is_some()
1036 {
1037 continue;
1038 }
1039
1040 let raw_session = async_fs::read(session_file.path())
1041 .await
1042 .map_err(CryptoboxMigrationError::wrap("reading session file"))?;
1043 let Ok(_) = Session::deserialise(&identity, &raw_session) else {
1045 continue;
1046 };
1047
1048 let keystore_session = ProteusSession {
1049 id: proteus_session_id,
1050 session: raw_session,
1051 };
1052
1053 keystore
1054 .save(keystore_session)
1055 .await
1056 .map_err(KeystoreError::wrap("saving proteus session"))?;
1057 }
1058
1059 use core_crypto_keystore::entities::ProteusPrekey;
1061
1062 use crate::CryptoboxMigrationError;
1063 let mut prekey_entries = async_fs::read_dir(prekey_dir)
1064 .await
1065 .map_err(CryptoboxMigrationError::wrap("reading prekey entries"))?;
1066 while let Some(prekey_file) = prekey_entries
1067 .try_next()
1068 .await
1069 .map_err(CryptoboxMigrationError::wrap("getting next prekey file"))?
1070 {
1071 let proteus_prekey_id = proteus_wasm::keys::PreKeyId::new(
1073 prekey_file
1074 .file_name()
1075 .to_string_lossy()
1076 .parse()
1077 .map_err(CryptoboxMigrationError::wrap("parsing prekey file name"))?,
1078 );
1079
1080 if keystore
1082 .find::<ProteusPrekey>(&proteus_prekey_id.value().to_le_bytes())
1083 .await
1084 .map_err(KeystoreError::wrap("finding proteus prekey by id"))?
1085 .is_some()
1086 {
1087 continue;
1088 }
1089
1090 let raw_prekey = async_fs::read(prekey_file.path())
1091 .await
1092 .map_err(CryptoboxMigrationError::wrap("reading prekey file"))?;
1093 if proteus_wasm::keys::PreKey::deserialise(&raw_prekey).is_ok() {
1095 let keystore_prekey = ProteusPrekey::from_raw(proteus_prekey_id.value(), raw_prekey);
1096 keystore
1097 .save(keystore_prekey)
1098 .await
1099 .map_err(KeystoreError::wrap("saving proteus prekey"))?;
1100 }
1101 }
1102
1103 Ok(())
1104 }
1105
1106 #[cfg(target_family = "wasm")]
1107 fn get_cbor_bytes_from_map(map: serde_json::map::Map<String, serde_json::Value>) -> Result<Vec<u8>> {
1108 use crate::{CryptoboxMigrationError, CryptoboxMigrationErrorKind};
1109
1110 let Some(js_value) = map.get("serialised") else {
1111 return Err(CryptoboxMigrationError::wrap("getting serialised cbor bytes from map")(
1112 CryptoboxMigrationErrorKind::MissingKeyInValue("serialised".to_string()),
1113 )
1114 .into());
1115 };
1116
1117 let Some(b64_value) = js_value.as_str() else {
1118 return Err(CryptoboxMigrationError::wrap("getting js value as string")(
1119 CryptoboxMigrationErrorKind::WrongValueType("string".to_string()),
1120 )
1121 .into());
1122 };
1123
1124 use base64::Engine as _;
1125 let cbor_bytes = base64::prelude::BASE64_STANDARD
1126 .decode(b64_value)
1127 .map_err(CryptoboxMigrationError::wrap("decoding cbor bytes"))?;
1128 Ok(cbor_bytes)
1129 }
1130
1131 #[cfg(target_family = "wasm")]
1132 async fn cryptobox_migrate_impl(keystore: &CryptoKeystore, path: &str) -> Result<()> {
1133 use rexie::{Rexie, TransactionMode};
1134
1135 use crate::{CryptoboxMigrationError, CryptoboxMigrationErrorKind};
1136 let local_identity_key = "local_identity";
1137 let local_identity_store_name = "keys";
1138 let prekeys_store_name = "prekeys";
1139 let sessions_store_name = "sessions";
1140
1141 let db = Rexie::builder(path)
1143 .build()
1144 .await
1145 .map_err(CryptoboxMigrationError::wrap("building rexie"))?;
1146
1147 let store_names = db.store_names();
1148
1149 let expected_stores = &[prekeys_store_name, sessions_store_name, local_identity_store_name];
1150
1151 if !expected_stores
1152 .iter()
1153 .map(ToString::to_string)
1154 .all(|s| store_names.contains(&s))
1155 {
1156 return Err(CryptoboxMigrationError::wrap("checking expected stores")(
1157 CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(path.into()),
1158 )
1159 .into());
1160 }
1161
1162 let mut proteus_identity = if let Some(store_kp) = keystore
1163 .find::<ProteusIdentity>(&[])
1164 .await
1165 .map_err(KeystoreError::wrap("finding proteus identity for empty id"))?
1166 {
1167 Some(
1168 proteus_wasm::keys::IdentityKeyPair::from_raw_key_pair(*store_kp.sk_raw(), *store_kp.pk_raw())
1169 .map_err(ProteusError::wrap("constructing identity keypair from raw"))?,
1170 )
1171 } else {
1172 let transaction = db
1173 .transaction(&[local_identity_store_name], TransactionMode::ReadOnly)
1174 .map_err(CryptoboxMigrationError::wrap("initializing rexie transaction"))?;
1175
1176 let identity_store = transaction
1177 .store(local_identity_store_name)
1178 .map_err(CryptoboxMigrationError::wrap("storing local identity store name"))?;
1179
1180 if let Some(cryptobox_js_value) = identity_store
1181 .get(local_identity_key.into())
1182 .await
1183 .map_err(CryptoboxMigrationError::wrap("getting local identity key js value"))?
1184 {
1185 let js_value: serde_json::map::Map<String, serde_json::Value> =
1186 serde_wasm_bindgen::from_value(cryptobox_js_value).map_err(CryptoboxMigrationError::wrap(
1187 "getting local identity key from identity store",
1188 ))?;
1189
1190 let kp_cbor = Self::get_cbor_bytes_from_map(js_value)?;
1191
1192 let kp = proteus_wasm::keys::IdentityKeyPair::deserialise(&kp_cbor)
1193 .map_err(ProteusError::wrap("deserializing identity keypair"))?;
1194
1195 let pk = kp.public_key.public_key.as_slice().to_vec();
1196
1197 let ks_identity = ProteusIdentity {
1198 sk: kp.secret_key.to_keypair_bytes().into(),
1199 pk,
1200 };
1201 keystore
1202 .save(ks_identity)
1203 .await
1204 .map_err(KeystoreError::wrap("saving proteus identity in keystore"))?;
1205
1206 Some(kp)
1207 } else {
1208 None
1209 }
1210 };
1211
1212 let Some(proteus_identity) = proteus_identity.take() else {
1213 return Err(CryptoboxMigrationError::wrap("taking proteus identity")(
1214 CryptoboxMigrationErrorKind::IdentityNotFound(path.into()),
1215 )
1216 .into());
1217 };
1218
1219 if store_names.contains(&sessions_store_name.to_string()) {
1220 let transaction = db
1221 .transaction(&[sessions_store_name], TransactionMode::ReadOnly)
1222 .map_err(CryptoboxMigrationError::wrap("starting rexie transaction"))?;
1223
1224 let sessions_store = transaction
1225 .store(sessions_store_name)
1226 .map_err(CryptoboxMigrationError::wrap("getting sessions store"))?;
1227
1228 let sessions = sessions_store
1229 .scan(None, None, None, None)
1230 .await
1231 .map_err(CryptoboxMigrationError::wrap("scanning sessions store for sessions"))?;
1232
1233 for (session_id, session_js_value) in sessions.into_iter().map(|(k, v)| (k.as_string().unwrap(), v)) {
1234 if keystore
1236 .find::<ProteusSession>(session_id.as_bytes())
1237 .await
1238 .map_err(KeystoreError::wrap("finding proteus session by id"))?
1239 .is_some()
1240 {
1241 continue;
1242 }
1243
1244 let js_value: serde_json::map::Map<String, serde_json::Value> =
1245 serde_wasm_bindgen::from_value(session_js_value).map_err(CryptoboxMigrationError::wrap(
1246 "converting session js value to serde map",
1247 ))?;
1248
1249 let session_cbor_bytes = Self::get_cbor_bytes_from_map(js_value)?;
1250
1251 if proteus_wasm::session::Session::deserialise(&proteus_identity, &session_cbor_bytes).is_ok() {
1253 let keystore_session = ProteusSession {
1254 id: session_id,
1255 session: session_cbor_bytes,
1256 };
1257
1258 keystore
1259 .save(keystore_session)
1260 .await
1261 .map_err(KeystoreError::wrap("saving keystore session"))?;
1262 }
1263 }
1264 }
1265
1266 if store_names.contains(&prekeys_store_name.to_string()) {
1267 use core_crypto_keystore::entities::ProteusPrekey;
1268
1269 let transaction = db
1270 .transaction(&[prekeys_store_name], TransactionMode::ReadOnly)
1271 .map_err(CryptoboxMigrationError::wrap("beginning rexie transaction"))?;
1272
1273 let prekeys_store = transaction
1274 .store(prekeys_store_name)
1275 .map_err(CryptoboxMigrationError::wrap("getting prekeys store"))?;
1276
1277 let prekeys = prekeys_store
1278 .scan(None, None, None, None)
1279 .await
1280 .map_err(CryptoboxMigrationError::wrap("scanning for prekeys"))?;
1281
1282 for (prekey_id, prekey_js_value) in prekeys
1283 .into_iter()
1284 .map(|(id, prekey_js_value)| (id.as_string().unwrap(), prekey_js_value))
1285 {
1286 let prekey_id: u16 = prekey_id
1287 .parse()
1288 .map_err(CryptoboxMigrationError::wrap("parsing prekey id"))?;
1289
1290 if keystore
1292 .find::<ProteusPrekey>(&prekey_id.to_le_bytes())
1293 .await
1294 .map_err(KeystoreError::wrap(
1295 "finding proteus prekey by id to check for existence",
1296 ))?
1297 .is_some()
1298 {
1299 continue;
1300 }
1301
1302 let js_value: serde_json::map::Map<String, serde_json::Value> =
1303 serde_wasm_bindgen::from_value(prekey_js_value)
1304 .map_err(CryptoboxMigrationError::wrap("converting prekey js value to serde map"))?;
1305
1306 let raw_prekey_cbor = Self::get_cbor_bytes_from_map(js_value)?;
1307
1308 if proteus_wasm::keys::PreKey::deserialise(&raw_prekey_cbor).is_ok() {
1310 let keystore_prekey = ProteusPrekey::from_raw(prekey_id, raw_prekey_cbor);
1311 keystore
1312 .save(keystore_prekey)
1313 .await
1314 .map_err(KeystoreError::wrap("saving proteus prekey"))?;
1315 }
1316 }
1317 }
1318
1319 Ok(())
1320 }
1321}
1322
1323#[cfg(test)]
1324mod tests {
1325 use crate::{
1326 prelude::{CertificateBundle, ClientIdentifier, MlsClientConfiguration, MlsCredentialType, Session},
1327 test_utils::{proteus_utils::*, x509::X509TestChain, *},
1328 };
1329
1330 use crate::prelude::INITIAL_KEYING_MATERIAL_COUNT;
1331 use proteus_traits::PreKeyStore;
1332 use wasm_bindgen_test::*;
1333
1334 use super::*;
1335
1336 wasm_bindgen_test_configure!(run_in_browser);
1337
1338 use core_crypto_keystore::DatabaseKey;
1339
1340 #[apply(all_cred_cipher)]
1341 #[wasm_bindgen_test]
1342 async fn cc_can_init(case: TestCase) {
1343 #[cfg(not(target_family = "wasm"))]
1344 let (path, db_file) = tmp_db_file();
1345 #[cfg(target_family = "wasm")]
1346 let (path, _) = tmp_db_file();
1347 let client_id = "alice".into();
1348 let cfg = MlsClientConfiguration::try_new(
1349 path,
1350 DatabaseKey::generate(),
1351 Some(client_id),
1352 vec![case.ciphersuite()],
1353 None,
1354 Some(INITIAL_KEYING_MATERIAL_COUNT),
1355 )
1356 .unwrap();
1357 let cc: CoreCrypto = Session::try_new(cfg).await.unwrap().into();
1358 let context = cc.new_transaction().await.unwrap();
1359 assert!(context.proteus_init().await.is_ok());
1360 assert!(context.proteus_new_prekey(1).await.is_ok());
1361 context.finish().await.unwrap();
1362 #[cfg(not(target_family = "wasm"))]
1363 drop(db_file);
1364 }
1365
1366 #[apply(all_cred_cipher)]
1367 #[wasm_bindgen_test]
1368 async fn cc_can_2_phase_init(case: TestCase) {
1369 #[cfg(not(target_family = "wasm"))]
1370 let (path, db_file) = tmp_db_file();
1371 #[cfg(target_family = "wasm")]
1372 let (path, _) = tmp_db_file();
1373 let cfg = MlsClientConfiguration::try_new(
1375 path,
1376 DatabaseKey::generate(),
1377 None,
1378 vec![case.ciphersuite()],
1379 None,
1380 Some(INITIAL_KEYING_MATERIAL_COUNT),
1381 )
1382 .unwrap();
1383 let cc: CoreCrypto = Session::try_new(cfg).await.unwrap().into();
1384 let transaction = cc.new_transaction().await.unwrap();
1385 let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
1386 x509_test_chain.register_with_central(&transaction).await;
1387 assert!(transaction.proteus_init().await.is_ok());
1388 assert!(transaction.proteus_new_prekey(1).await.is_ok());
1390 let client_id = "alice";
1392 let identifier = match case.credential_type {
1393 MlsCredentialType::Basic => ClientIdentifier::Basic(client_id.into()),
1394 MlsCredentialType::X509 => {
1395 CertificateBundle::rand_identifier(client_id, &[x509_test_chain.find_local_intermediate_ca()])
1396 }
1397 };
1398 transaction
1399 .mls_init(
1400 identifier,
1401 vec![case.ciphersuite()],
1402 Some(INITIAL_KEYING_MATERIAL_COUNT),
1403 )
1404 .await
1405 .unwrap();
1406 assert_eq!(
1408 transaction
1409 .get_or_create_client_keypackages(case.ciphersuite(), case.credential_type, 2)
1410 .await
1411 .unwrap()
1412 .len(),
1413 2
1414 );
1415 #[cfg(not(target_family = "wasm"))]
1416 drop(db_file);
1417 }
1418
1419 #[async_std::test]
1420 #[wasm_bindgen_test]
1421 async fn can_init() {
1422 #[cfg(not(target_family = "wasm"))]
1423 let (path, db_file) = tmp_db_file();
1424 #[cfg(target_family = "wasm")]
1425 let (path, _) = tmp_db_file();
1426 let key = DatabaseKey::generate();
1427 let keystore = core_crypto_keystore::Connection::open_with_key(&path, &key)
1428 .await
1429 .unwrap();
1430 keystore.new_transaction().await.unwrap();
1431 let central = ProteusCentral::try_new(&keystore).await.unwrap();
1432 let identity = (*central.proteus_identity).clone();
1433 keystore.commit_transaction().await.unwrap();
1434
1435 let keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1436 .await
1437 .unwrap();
1438 keystore.new_transaction().await.unwrap();
1439 let central = ProteusCentral::try_new(&keystore).await.unwrap();
1440 keystore.commit_transaction().await.unwrap();
1441 assert_eq!(identity, *central.proteus_identity);
1442
1443 keystore.wipe().await.unwrap();
1444 #[cfg(not(target_family = "wasm"))]
1445 drop(db_file);
1446 }
1447
1448 #[async_std::test]
1449 #[wasm_bindgen_test]
1450 async fn can_talk_with_proteus() {
1451 #[cfg(not(target_family = "wasm"))]
1452 let (path, db_file) = tmp_db_file();
1453 #[cfg(target_family = "wasm")]
1454 let (path, _) = tmp_db_file();
1455
1456 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1457
1458 let key = DatabaseKey::generate();
1459 let mut keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1460 .await
1461 .unwrap();
1462 keystore.new_transaction().await.unwrap();
1463
1464 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
1465
1466 let mut bob = CryptoboxLike::init();
1467 let bob_pk_bundle = bob.new_prekey();
1468
1469 alice
1470 .session_from_prekey(&session_id, &bob_pk_bundle.serialise().unwrap())
1471 .await
1472 .unwrap();
1473
1474 let message = b"Hello world";
1475
1476 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
1477 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1478 assert_eq!(decrypted, message);
1479
1480 let encrypted = bob.encrypt(&session_id, message);
1481 let decrypted = alice.decrypt(&mut keystore, &session_id, &encrypted).await.unwrap();
1482 assert_eq!(decrypted, message);
1483
1484 keystore.commit_transaction().await.unwrap();
1485 keystore.wipe().await.unwrap();
1486 #[cfg(not(target_family = "wasm"))]
1487 drop(db_file);
1488 }
1489
1490 #[async_std::test]
1491 #[wasm_bindgen_test]
1492 async fn can_produce_proteus_consumed_prekeys() {
1493 #[cfg(not(target_family = "wasm"))]
1494 let (path, db_file) = tmp_db_file();
1495 #[cfg(target_family = "wasm")]
1496 let (path, _) = tmp_db_file();
1497
1498 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1499
1500 let key = DatabaseKey::generate();
1501 let mut keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1502 .await
1503 .unwrap();
1504 keystore.new_transaction().await.unwrap();
1505 let mut alice = ProteusCentral::try_new(&keystore).await.unwrap();
1506
1507 let mut bob = CryptoboxLike::init();
1508
1509 let alice_prekey_bundle_ser = alice.new_prekey(1, &keystore).await.unwrap();
1510
1511 bob.init_session_from_prekey_bundle(&session_id, &alice_prekey_bundle_ser);
1512 let message = b"Hello world!";
1513 let encrypted = bob.encrypt(&session_id, message);
1514
1515 let (_, decrypted) = alice
1516 .session_from_message(&mut keystore, &session_id, &encrypted)
1517 .await
1518 .unwrap();
1519
1520 assert_eq!(message, decrypted.as_slice());
1521
1522 let encrypted = alice.encrypt(&mut keystore, &session_id, message).await.unwrap();
1523 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1524
1525 assert_eq!(message, decrypted.as_slice());
1526 keystore.commit_transaction().await.unwrap();
1527 keystore.wipe().await.unwrap();
1528 #[cfg(not(target_family = "wasm"))]
1529 drop(db_file);
1530 }
1531
1532 #[async_std::test]
1533 #[wasm_bindgen_test]
1534 async fn auto_prekeys_are_sequential() {
1535 use core_crypto_keystore::entities::ProteusPrekey;
1536 const GAP_AMOUNT: u16 = 5;
1537 const ID_TEST_RANGE: std::ops::RangeInclusive<u16> = 1..=30;
1538
1539 #[cfg(not(target_family = "wasm"))]
1540 let (path, db_file) = tmp_db_file();
1541 #[cfg(target_family = "wasm")]
1542 let (path, _) = tmp_db_file();
1543
1544 let key = DatabaseKey::generate();
1545 let keystore = core_crypto_keystore::Connection::open_with_key(path, &key)
1546 .await
1547 .unwrap();
1548 keystore.new_transaction().await.unwrap();
1549 let alice = ProteusCentral::try_new(&keystore).await.unwrap();
1550
1551 for i in ID_TEST_RANGE {
1552 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1553 assert_eq!(i, pk_id);
1554 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1555 assert_eq!(prekey.prekey_id.value(), pk_id);
1556 }
1557
1558 use rand::Rng as _;
1559 let mut rng = rand::thread_rng();
1560 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
1561 gap_ids.sort();
1562 gap_ids.dedup();
1563 while gap_ids.len() < GAP_AMOUNT as usize {
1564 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
1565 gap_ids.sort();
1566 gap_ids.dedup();
1567 }
1568 for gap_id in gap_ids.iter() {
1569 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
1570 }
1571
1572 gap_ids.sort();
1573
1574 for gap_id in gap_ids.iter() {
1575 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1576 assert_eq!(pk_id, *gap_id);
1577 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1578 assert_eq!(prekey.prekey_id.value(), *gap_id);
1579 }
1580
1581 let mut gap_ids: Vec<u16> = (0..GAP_AMOUNT).map(|_| rng.gen_range(ID_TEST_RANGE)).collect();
1582 gap_ids.sort();
1583 gap_ids.dedup();
1584 while gap_ids.len() < GAP_AMOUNT as usize {
1585 gap_ids.push(rng.gen_range(ID_TEST_RANGE));
1586 gap_ids.sort();
1587 gap_ids.dedup();
1588 }
1589 for gap_id in gap_ids.iter() {
1590 keystore.remove::<ProteusPrekey, _>(gap_id.to_le_bytes()).await.unwrap();
1591 }
1592
1593 let potential_range = *ID_TEST_RANGE.end()..=(*ID_TEST_RANGE.end() * 2);
1594 let potential_range_check = potential_range.clone();
1595 for _ in potential_range {
1596 let (pk_id, pkb) = alice.new_prekey_auto(&keystore).await.unwrap();
1597 assert!(gap_ids.contains(&pk_id) || potential_range_check.contains(&pk_id));
1598 let prekey = proteus_wasm::keys::PreKeyBundle::deserialise(&pkb).unwrap();
1599 assert_eq!(prekey.prekey_id.value(), pk_id);
1600 }
1601 keystore.commit_transaction().await.unwrap();
1602 keystore.wipe().await.unwrap();
1603 #[cfg(not(target_family = "wasm"))]
1604 drop(db_file);
1605 }
1606
1607 #[cfg(all(feature = "cryptobox-migrate", not(target_family = "wasm")))]
1608 #[async_std::test]
1609 async fn can_import_cryptobox() {
1610 use crate::CryptoboxMigrationErrorKind;
1611
1612 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1613
1614 let cryptobox_folder = tempfile::tempdir().unwrap();
1615 let alice = cryptobox::CBox::file_open(cryptobox_folder.path()).unwrap();
1616 let alice_fingerprint = alice.fingerprint();
1617
1618 let mut bob = CryptoboxLike::init();
1619 let bob_pk_bundle = bob.new_prekey();
1620
1621 let alice_pk_id = proteus::keys::PreKeyId::new(1u16);
1622 let alice_pk = alice.new_prekey(alice_pk_id).unwrap();
1623
1624 let mut alice_session = alice
1625 .session_from_prekey(session_id.clone(), &bob_pk_bundle.serialise().unwrap())
1626 .unwrap();
1627
1628 let message = b"Hello world!";
1629
1630 let alice_msg_envelope = alice_session.encrypt(message).unwrap();
1631 let decrypted = bob.decrypt(&session_id, &alice_msg_envelope).await;
1632 assert_eq!(decrypted, message);
1633
1634 alice.session_save(&mut alice_session).unwrap();
1635
1636 let encrypted = bob.encrypt(&session_id, &message[..]);
1637 let decrypted = alice_session.decrypt(&encrypted).unwrap();
1638 assert_eq!(decrypted, message);
1639
1640 alice.session_save(&mut alice_session).unwrap();
1641
1642 drop(alice);
1643
1644 let keystore_dir = tempfile::tempdir().unwrap();
1645 let keystore_file = keystore_dir.path().join("keystore");
1646
1647 let key = DatabaseKey::generate();
1648 let mut keystore =
1649 core_crypto_keystore::Connection::open_with_key(keystore_file.as_os_str().to_string_lossy(), &key)
1650 .await
1651 .unwrap();
1652 keystore.new_transaction().await.unwrap();
1653
1654 let Err(crate::Error::CryptoboxMigration(crate::CryptoboxMigrationError {
1655 source: CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(_),
1656 ..
1657 })) = ProteusCentral::cryptobox_migrate(&keystore, "invalid path").await
1658 else {
1659 panic!("ProteusCentral::cryptobox_migrate did not throw an error on invalid path");
1660 };
1661
1662 ProteusCentral::cryptobox_migrate(&keystore, &cryptobox_folder.path().to_string_lossy())
1663 .await
1664 .unwrap();
1665
1666 let mut proteus_central = ProteusCentral::try_new(&keystore).await.unwrap();
1667
1668 assert_eq!(proteus_central.fingerprint(), alice_fingerprint);
1670
1671 let alice_new_session_lock = proteus_central
1673 .session(&session_id, &mut keystore)
1674 .await
1675 .unwrap()
1676 .unwrap();
1677 let alice_new_session = alice_new_session_lock.read().await;
1678 assert_eq!(
1679 alice_new_session.session.local_identity().fingerprint(),
1680 alice_session.fingerprint_local()
1681 );
1682 assert_eq!(
1683 alice_new_session.session.remote_identity().fingerprint(),
1684 alice_session.fingerprint_remote()
1685 );
1686
1687 drop(alice_new_session);
1688 drop(alice_new_session_lock);
1689
1690 let keystore_pk = keystore.prekey(1).await.unwrap().unwrap();
1692 let keystore_pk = proteus_wasm::keys::PreKey::deserialise(&keystore_pk).unwrap();
1693 assert_eq!(alice_pk.prekey_id.value(), keystore_pk.key_id.value());
1694 assert_eq!(
1695 alice_pk.public_key.fingerprint(),
1696 keystore_pk.key_pair.public_key.fingerprint()
1697 );
1698
1699 let encrypted = proteus_central
1701 .encrypt(&mut keystore, &session_id, &message[..])
1702 .await
1703 .unwrap();
1704 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1705
1706 assert_eq!(&decrypted, &message[..]);
1707
1708 let encrypted = bob.encrypt(&session_id, &message[..]);
1713 let decrypted = proteus_central
1714 .decrypt(&mut keystore, &session_id, &encrypted)
1715 .await
1716 .unwrap();
1717 assert_eq!(&decrypted, &message[..]);
1718
1719 proteus_central.session_save(&mut keystore, &session_id).await.unwrap();
1720 keystore.commit_transaction().await.unwrap();
1721 keystore.wipe().await.unwrap();
1722 }
1723
1724 cfg_if::cfg_if! {
1725 if #[cfg(all(feature = "cryptobox-migrate", target_family = "wasm"))] {
1726 const CRYPTOBOX_JS_DBNAME: &str = "cryptobox-migrate-test";
1728 fn run_cryptobox(alice: CryptoboxLike) -> js_sys::Promise {
1732 wasm_bindgen_futures::future_to_promise(async move {
1733 use rexie::{Rexie, ObjectStore, TransactionMode};
1734 use wasm_bindgen::JsValue;
1735
1736 Rexie::builder(CRYPTOBOX_JS_DBNAME)
1738 .delete()
1739 .await.map_err(|err| err.to_string())?;
1740
1741 let rexie = Rexie::builder(CRYPTOBOX_JS_DBNAME)
1742 .version(1)
1743 .add_object_store(ObjectStore::new("keys").auto_increment(false))
1744 .add_object_store(ObjectStore::new("prekeys").auto_increment(false))
1745 .add_object_store(ObjectStore::new("sessions").auto_increment(false))
1746 .build()
1747 .await.map_err(|err| err.to_string())?;
1748
1749 let transaction = rexie.transaction(&["keys"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1751 let store = transaction.store("keys").map_err(|err| err.to_string())?;
1752
1753 use base64::Engine as _;
1754 let json = serde_json::json!({
1755 "created": 0,
1756 "id": "local_identity",
1757 "serialised": base64::prelude::BASE64_STANDARD.encode(alice.identity.serialise().unwrap()),
1758 "version": "1.0"
1759 });
1760 let js_value = serde_wasm_bindgen::to_value(&json)?;
1761
1762 store.add(&js_value, Some(&JsValue::from_str("local_identity"))).await.map_err(|err| err.to_string())?;
1763
1764 let transaction = rexie.transaction(&["prekeys"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1766 let store = transaction.store("prekeys").map_err(|err| err.to_string())?;
1767 for prekey in alice.prekeys.0.into_iter() {
1768 let id = prekey.key_id.value().to_string();
1769 let json = serde_json::json!({
1770 "created": 0,
1771 "id": &id,
1772 "serialised": base64::prelude::BASE64_STANDARD.encode(prekey.serialise().unwrap()),
1773 "version": "1.0"
1774 });
1775 let js_value = serde_wasm_bindgen::to_value(&json)?;
1776 store.add(&js_value, Some(&JsValue::from_str(&id))).await.map_err(|err| err.to_string())?;
1777 }
1778
1779 let transaction = rexie.transaction(&["sessions"], TransactionMode::ReadWrite).map_err(|err| err.to_string())?;
1781 let store = transaction.store("sessions").map_err(|err| err.to_string())?;
1782 for (session_id, session) in alice.sessions.into_iter() {
1783 let json = serde_json::json!({
1784 "created": 0,
1785 "id": session_id,
1786 "serialised": base64::prelude::BASE64_STANDARD.encode(session.serialise().unwrap()),
1787 "version": "1.0"
1788 });
1789
1790 let js_value = serde_wasm_bindgen::to_value(&json)?;
1791 store.add(&js_value, Some(&JsValue::from_str(&session_id))).await.map_err(|err| err.to_string())?;
1792 }
1793
1794 Ok(JsValue::UNDEFINED)
1795 })
1796 }
1797
1798 #[wasm_bindgen_test]
1799 async fn can_import_cryptobox() {
1800 let session_id = uuid::Uuid::new_v4().hyphenated().to_string();
1801
1802 let mut alice = CryptoboxLike::init();
1803 let alice_fingerprint = alice.fingerprint();
1804 const PREKEY_COUNT: usize = 10;
1805 let prekey_iter_range = 0..PREKEY_COUNT;
1806 let prekey_bundles: Vec<proteus_wasm::keys::PreKeyBundle> = prekey_iter_range.clone().map(|_| alice.new_prekey()).collect();
1808
1809 let mut bob = CryptoboxLike::init();
1811 let bob_pk_bundle = bob.new_prekey();
1812 let message = b"Hello world!";
1813
1814 alice.init_session_from_prekey_bundle(&session_id, &bob_pk_bundle.serialise().unwrap());
1815 let alice_to_bob_message = alice.encrypt(&session_id, message);
1816 let decrypted = bob.decrypt(&session_id, &alice_to_bob_message).await;
1817 assert_eq!(&message[..], decrypted.as_slice());
1818
1819 let bob_to_alice_message = bob.encrypt(&session_id, message);
1820 let decrypted = alice.decrypt(&session_id, &bob_to_alice_message).await;
1821 assert_eq!(&message[..], decrypted.as_slice());
1822
1823 let alice_session = alice.session(&session_id);
1824 let alice_session_fingerprint_local = alice_session.local_identity().fingerprint();
1825 let alice_session_fingerprint_remote = alice_session.remote_identity().fingerprint();
1826
1827 let _ = wasm_bindgen_futures::JsFuture::from(run_cryptobox(alice)).await.unwrap();
1828
1829 use sha2::Digest as _;
1830 let old_key = "test";
1831 let new_key = DatabaseKey::try_from(sha2::Sha256::digest(old_key).as_slice()).unwrap();
1832
1833 let name = format!("{CRYPTOBOX_JS_DBNAME}-imported");
1834 let _ = core_crypto_keystore::connection::platform::open_and_migrate_pre_v4(&name, old_key).await;
1835
1836 core_crypto_keystore::Connection::migrate_db_key_type_to_bytes(&name, old_key, &new_key).await.unwrap();
1837
1838 let mut keystore = core_crypto_keystore::Connection::open_with_key(&name, &new_key).await.unwrap();
1839 keystore.new_transaction().await.unwrap();
1840 let Err(crate::Error::CryptoboxMigration(crate::CryptoboxMigrationError{
1841 source: crate::CryptoboxMigrationErrorKind::ProvidedPathDoesNotExist(_),
1842 ..
1843 })) = ProteusCentral::cryptobox_migrate(&keystore, "invalid path").await else {
1844 panic!("ProteusCentral::cryptobox_migrate did not throw an error on invalid path");
1845 };
1846
1847 ProteusCentral::cryptobox_migrate(&keystore, CRYPTOBOX_JS_DBNAME).await.unwrap();
1848
1849 let mut proteus_central = ProteusCentral::try_new(&keystore).await.unwrap();
1850
1851 assert_eq!(proteus_central.fingerprint(), alice_fingerprint);
1853
1854 let alice_new_session_lock = proteus_central
1856 .session(&session_id, &mut keystore)
1857 .await
1858 .unwrap()
1859 .unwrap();
1860 let alice_new_session = alice_new_session_lock.read().await;
1861 assert_eq!(
1862 alice_new_session.session.local_identity().fingerprint(),
1863 alice_session_fingerprint_local
1864 );
1865 assert_eq!(
1866 alice_new_session.session.remote_identity().fingerprint(),
1867 alice_session_fingerprint_remote
1868 );
1869
1870 drop(alice_new_session);
1871 drop(alice_new_session_lock);
1872
1873 for i in prekey_iter_range {
1875 let prekey_id = (i + 1) as u16;
1876 let keystore_pk = keystore.prekey(prekey_id).await.unwrap().unwrap();
1877 let keystore_pk = proteus_wasm::keys::PreKey::deserialise(&keystore_pk).unwrap();
1878 let alice_pk = &prekey_bundles[i];
1879
1880 assert_eq!(alice_pk.prekey_id.value(), keystore_pk.key_id.value());
1881 assert_eq!(
1882 alice_pk.public_key.fingerprint(),
1883 keystore_pk.key_pair.public_key.fingerprint()
1884 );
1885 }
1886
1887
1888 let encrypted = proteus_central.encrypt(&mut keystore, &session_id, &message[..]).await.unwrap();
1890 let decrypted = bob.decrypt(&session_id, &encrypted).await;
1891
1892 assert_eq!(&decrypted, &message[..]);
1893
1894 let encrypted = bob.encrypt(&session_id, &message[..]);
1896 let decrypted = proteus_central
1897 .decrypt(&mut keystore, &session_id, &encrypted)
1898 .await
1899 .unwrap();
1900 assert_eq!(&decrypted, &message[..]);
1901 keystore.commit_transaction().await.unwrap();
1902
1903 keystore.wipe().await.unwrap();
1904 }
1905 }
1906 }
1907}