1use std::{
18 collections::{BTreeMap, HashMap},
19 ops::Deref,
20 sync::{Arc, LazyLock, Once},
21};
22
23use log::{
24 Level, LevelFilter, Metadata, Record,
25 kv::{self, Key, Value, VisitSource},
26};
27use log_reload::ReloadLog;
28use tls_codec::Deserialize;
29
30use self::context::CoreCryptoContext;
31use crate::proteus_impl;
32use core_crypto::mls::conversation::Conversation as _;
33pub use core_crypto::prelude::ConversationId;
34use core_crypto::{
35 InnermostErrorMessage, RecursiveError,
36 prelude::{
37 EntropySeed, MlsBufferedConversationDecryptMessage, MlsCentral, MlsCentralConfiguration, MlsCiphersuite,
38 MlsCommitBundle, MlsConversationDecryptMessage, MlsCustomConfiguration, MlsGroupInfoBundle, MlsProposalBundle,
39 VerifiableGroupInfo,
40 },
41};
42
43pub mod context;
44mod epoch_observer;
45
46#[allow(dead_code)]
47pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION");
48
49#[uniffi::export]
50pub fn version() -> String {
51 VERSION.to_string()
52}
53
54#[derive(uniffi::Record)]
55pub struct BuildMetadata {
57 pub timestamp: String,
59 pub cargo_debug: String,
61 pub cargo_features: String,
63 pub opt_level: String,
65 pub target_triple: String,
67 pub git_branch: String,
69 pub git_describe: String,
71 pub git_sha: String,
73 pub git_dirty: String,
75}
76
77#[uniffi::export]
78pub fn build_metadata() -> BuildMetadata {
79 BuildMetadata {
80 timestamp: core_crypto::BUILD_METADATA.timestamp.to_string(),
81 cargo_debug: core_crypto::BUILD_METADATA.cargo_debug.to_string(),
82 cargo_features: core_crypto::BUILD_METADATA.cargo_features.to_string(),
83 opt_level: core_crypto::BUILD_METADATA.opt_level.to_string(),
84 target_triple: core_crypto::BUILD_METADATA.target_triple.to_string(),
85 git_branch: core_crypto::BUILD_METADATA.git_branch.to_string(),
86 git_describe: core_crypto::BUILD_METADATA.git_describe.to_string(),
87 git_sha: core_crypto::BUILD_METADATA.git_sha.to_string(),
88 git_dirty: core_crypto::BUILD_METADATA.git_dirty.to_string(),
89 }
90}
91
92#[derive(Debug, thiserror::Error, uniffi::Error)]
93pub enum MlsError {
94 #[error("Conversation already exists")]
95 ConversationAlreadyExists(core_crypto::prelude::ConversationId),
96 #[error("We already decrypted this message once")]
97 DuplicateMessage,
98 #[error("Incoming message is for a future epoch. We will buffer it until the commit for that epoch arrives")]
99 BufferedFutureMessage,
100 #[error("Incoming message is from an epoch too far in the future to buffer.")]
101 WrongEpoch,
102 #[error(
103 "Incoming message is a commit for which we have not yet received all the proposals. Buffering until all proposals have arrived."
104 )]
105 BufferedCommit,
106 #[error("The epoch in which message was encrypted is older than allowed")]
107 MessageEpochTooOld,
108 #[error("Tried to decrypt a commit created by self which is likely to have been replayed by the DS")]
109 SelfCommitIgnored,
110 #[error(
111 "You tried to join with an external commit but did not merge it yet. We will reapply this message for you when you merge your external commit"
112 )]
113 UnmergedPendingGroup,
114 #[error("The received proposal is deemed stale and is from an older epoch.")]
115 StaleProposal,
116 #[error("The received commit is deemed stale and is from an older epoch.")]
117 StaleCommit,
118 #[error(
123 "Although this Welcome seems valid, the local KeyPackage it references has already been deleted locally. Join this group with an external commit"
124 )]
125 OrphanWelcome,
126 #[error("Message rejected by the delivery service. Reason: {reason}")]
128 MessageRejected {
129 reason: String,
131 },
132 #[error("{0}")]
133 Other(String),
134}
135
136impl From<core_crypto::MlsError> for MlsError {
137 #[inline]
138 fn from(e: core_crypto::MlsError) -> Self {
139 Self::Other(e.innermost_error_message())
140 }
141}
142
143#[cfg(feature = "proteus")]
144#[derive(Debug, thiserror::Error, uniffi::Error)]
145pub enum ProteusError {
146 #[error("The requested session was not found")]
147 SessionNotFound,
148 #[error("We already decrypted this message once")]
149 DuplicateMessage,
150 #[error("The remote identity has changed")]
151 RemoteIdentityChanged,
152 #[error("Another Proteus error occurred but the details are probably irrelevant to clients")]
153 Other(u16),
154}
155
156#[cfg(feature = "proteus")]
157impl ProteusError {
158 pub fn from_error_code(code: u16) -> Self {
159 match code {
160 102 => Self::SessionNotFound,
161 204 => Self::RemoteIdentityChanged,
162 209 => Self::DuplicateMessage,
163 _ => Self::Other(code),
164 }
165 }
166
167 pub fn error_code(&self) -> u16 {
168 match self {
169 Self::SessionNotFound => 102,
170 Self::RemoteIdentityChanged => 204,
171 Self::DuplicateMessage => 209,
172 Self::Other(code) => *code,
173 }
174 }
175}
176
177#[cfg(feature = "proteus")]
178impl From<core_crypto::ProteusError> for ProteusError {
179 fn from(value: core_crypto::ProteusError) -> Self {
180 type SessionError = proteus_wasm::session::Error<core_crypto_keystore::CryptoKeystoreError>;
181 match value.source {
182 core_crypto::ProteusErrorKind::ProteusSessionError(SessionError::InternalError(
183 proteus_wasm::internal::types::InternalError::NoSessionForTag,
184 )) => Self::SessionNotFound,
185 core_crypto::ProteusErrorKind::ProteusSessionError(SessionError::DuplicateMessage) => {
186 Self::DuplicateMessage
187 }
188 core_crypto::ProteusErrorKind::ProteusSessionError(SessionError::RemoteIdentityChanged) => {
189 Self::RemoteIdentityChanged
190 }
191 _ => Self::Other(value.source.error_code().unwrap_or_default()),
192 }
193 }
194}
195
196#[derive(Debug, thiserror::Error, uniffi::Error)]
197pub enum CoreCryptoError {
198 #[error(transparent)]
199 Mls(#[from] MlsError),
200 #[cfg(feature = "proteus")]
201 #[error(transparent)]
202 Proteus(#[from] ProteusError),
203 #[error("End to end identity error: {0}")]
204 E2eiError(String),
205 #[error("error from client: {0}")]
206 ClientError(String),
207 #[error("{0}")]
208 Other(String),
209}
210
211fn log_error(error: &dyn std::error::Error) {
222 let chain = {
224 let mut error = error;
225 let mut chain = Vec::new();
226 while let Some(inner) = error.source() {
227 chain.push(inner.to_string());
228 error = inner;
229 }
230 chain
231 };
232 let msg = error.to_string();
233 let err = serde_json::json!({"msg": msg, "chain": chain});
234 log::warn!(target: "core-crypto", err:serde; "core-crypto returning this error across ffi; see recent log messages for context");
238}
239
240impl From<RecursiveError> for CoreCryptoError {
241 fn from(error: RecursiveError) -> Self {
242 log_error(&error);
243
244 let innermost = {
246 let mut err: &dyn std::error::Error = &error;
247 while let Some(inner) = err.source() {
248 err = inner;
249 }
250 err
251 };
252
253 if let Some(err) = innermost.downcast_ref::<core_crypto::e2e_identity::Error>() {
254 return CoreCryptoError::E2eiError(err.to_string());
255 }
256
257 macro_rules! matches_option {
267 ($val:expr, $pattern:pat $(if $guard:expr)? => $out:expr) => {
268 match ($val) {
269 $pattern $(if $guard)? => Some($out),
270 _ => None,
271 }
272 };
273 }
274
275 macro_rules! match_heterogenous {
279 ($err:expr => {
280 $( $pattern:pat $(if $guard:expr)? => $var:expr, )*
281 ||=> $default:expr,
282 }) => {{
283 if false {unreachable!()}
284 $(
285 else if let Some(v) = matches_option!($err.downcast_ref(), Some($pattern) $(if $guard)? => $var) {
286 v
287 }
288 )*
289 else {
290 $default
291 }
292 }};
293 }
294
295 match_heterogenous!(innermost => {
296 core_crypto::LeafError::ConversationAlreadyExists(id) => MlsError::ConversationAlreadyExists(id.clone()).into(),
297 core_crypto::mls::conversation::Error::BufferedFutureMessage{..} => MlsError::BufferedFutureMessage.into(),
298 core_crypto::mls::conversation::Error::DuplicateMessage => MlsError::DuplicateMessage.into(),
299 core_crypto::mls::conversation::Error::MessageEpochTooOld => MlsError::MessageEpochTooOld.into(),
300 core_crypto::mls::conversation::Error::SelfCommitIgnored => MlsError::SelfCommitIgnored.into(),
301 core_crypto::mls::conversation::Error::StaleCommit => MlsError::StaleCommit.into(),
302 core_crypto::mls::conversation::Error::StaleProposal => MlsError::StaleProposal.into(),
303 core_crypto::mls::conversation::Error::UnbufferedFarFutureMessage => MlsError::WrongEpoch.into(),
304 core_crypto::mls::conversation::Error::BufferedCommit => MlsError::BufferedCommit.into(),
305 core_crypto::mls::conversation::Error::MessageRejected { reason } => MlsError::MessageRejected { reason: reason.clone() }.into(),
306 core_crypto::mls::conversation::Error::OrphanWelcome => MlsError::OrphanWelcome.into(),
307 core_crypto::mls::conversation::Error::BufferedForPendingConversation => MlsError::UnmergedPendingGroup.into(),
311 ||=> MlsError::Other(error.innermost_error_message()).into(),
312 })
313 }
314}
315
316impl From<core_crypto::Error> for CoreCryptoError {
322 fn from(error: core_crypto::Error) -> Self {
323 log_error(&error);
324
325 #[cfg(feature = "proteus")]
327 if let core_crypto::Error::Proteus(proteus) = &error {
328 if let Some(code) = proteus.source.error_code() {
329 if code != 0 {
330 return Self::Proteus(ProteusError::from_error_code(code));
331 }
332 }
333 }
334 match error {
335 core_crypto::Error::ProteusNotInitialized => Self::Other("proteus not initialized".to_string()),
336 core_crypto::Error::Proteus(proteus) => Self::Other(proteus.innermost_error_message()),
337 core_crypto::Error::Mls(mls) => Self::Mls(MlsError::from(mls)),
338 core_crypto::Error::InvalidContext => Self::Other(error.to_string()),
339 core_crypto::Error::MlsTransportNotProvided => Self::Other(error.to_string()),
340 core_crypto::Error::ErrorDuringMlsTransport(error_message) => Self::Other(error_message),
341 core_crypto::Error::Keystore(keystore_error) => Self::Other(keystore_error.innermost_error_message()),
342 core_crypto::Error::CryptoboxMigration(cryptobox) => Self::Other(cryptobox.innermost_error_message()),
343 core_crypto::Error::Recursive(recursive_error) => recursive_error.into(),
344 core_crypto::Error::FeatureDisabled(_) => Self::Other(error.to_string()),
345 }
346 }
347}
348
349macro_rules! impl_from_via_recursive_error {
357 ($($t:ty),+ $(,)?) => {
358 $(
359 impl From<$t> for CoreCryptoError {
360 fn from(error: $t) -> Self {
361 use core_crypto::ToRecursiveError;
362 error
363 .construct_recursive("this context string does not matter and gets immediately stripped")
364 .into()
365 }
366 }
367 )*
368 };
369}
370
371impl_from_via_recursive_error!(
372 core_crypto::mls::Error,
373 core_crypto::mls::conversation::Error,
374 core_crypto::e2e_identity::Error,
375);
376
377impl From<uniffi::UnexpectedUniFFICallbackError> for CoreCryptoError {
378 fn from(value: uniffi::UnexpectedUniFFICallbackError) -> Self {
379 Self::ClientError(value.reason)
380 }
381}
382
383impl CoreCryptoError {
384 fn generic<E>() -> impl FnOnce(E) -> Self
385 where
386 E: ToString,
387 {
388 |err| Self::Other(err.to_string())
389 }
390}
391
392type CoreCryptoResult<T> = Result<T, CoreCryptoError>;
393
394#[derive(Debug, Clone, Eq, Hash, PartialEq, derive_more::From)]
395pub struct ClientId(core_crypto::prelude::ClientId);
396
397uniffi::custom_type!(ClientId, Vec<u8>, {
398 lower: |id| id.0.to_vec(),
399 try_lift: |vec| Ok(Self(core_crypto::prelude::ClientId::from(vec)))
400});
401
402#[derive(Debug, Clone, derive_more::From, derive_more::Into)]
403pub struct NewCrlDistributionPoints(Option<Vec<String>>);
404
405uniffi::custom_newtype!(NewCrlDistributionPoints, Option<Vec<String>>);
406
407#[allow(non_camel_case_types)]
408#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
409#[repr(u16)]
410pub enum CiphersuiteName {
411 MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 = 0x0001,
413 MLS_128_DHKEMP256_AES128GCM_SHA256_P256 = 0x0002,
415 MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 = 0x0003,
417 MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448 = 0x0004,
419 MLS_256_DHKEMP521_AES256GCM_SHA512_P521 = 0x0005,
421 MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448 = 0x0006,
423 MLS_256_DHKEMP384_AES256GCM_SHA384_P384 = 0x0007,
425}
426
427#[derive(Debug, Clone)]
428pub struct Ciphersuite(core_crypto::prelude::CiphersuiteName);
429
430uniffi::custom_type!(Ciphersuite, u16, {
431 lower: |ciphersuite| (&ciphersuite.0).into(),
432 try_lift: |val| {
433 core_crypto::prelude::CiphersuiteName::try_from(val)
434 .map(Into::into)
435 .map_err(Into::into)
436 }
437});
438
439impl From<core_crypto::prelude::CiphersuiteName> for Ciphersuite {
440 fn from(cs: core_crypto::prelude::CiphersuiteName) -> Self {
441 Self(cs)
442 }
443}
444
445impl From<Ciphersuite> for core_crypto::prelude::CiphersuiteName {
446 fn from(cs: Ciphersuite) -> Self {
447 cs.0
448 }
449}
450
451impl From<Ciphersuite> for MlsCiphersuite {
452 fn from(cs: Ciphersuite) -> Self {
453 cs.0.into()
454 }
455}
456
457#[derive(Debug, Default, Clone)]
458pub struct Ciphersuites(Vec<core_crypto::prelude::CiphersuiteName>);
459
460impl From<Vec<core_crypto::prelude::CiphersuiteName>> for Ciphersuites {
461 fn from(cs: Vec<core_crypto::prelude::CiphersuiteName>) -> Self {
462 Self(cs)
463 }
464}
465
466impl From<Ciphersuites> for Vec<core_crypto::prelude::CiphersuiteName> {
467 fn from(cs: Ciphersuites) -> Self {
468 cs.0
469 }
470}
471
472impl<'a> From<&'a Ciphersuites> for Vec<MlsCiphersuite> {
473 fn from(cs: &'a Ciphersuites) -> Self {
474 cs.0.iter().fold(Vec::with_capacity(cs.0.len()), |mut acc, c| {
475 acc.push((*c).into());
476 acc
477 })
478 }
479}
480
481uniffi::custom_type!(Ciphersuites, Vec<u16>, {
482 lower: |cs| cs.0.into_iter().map(|c| (&c).into()).collect(),
483 try_lift: |val| {
484 val.iter().try_fold(Ciphersuites(vec![]), |mut acc, c| -> uniffi::Result<Self> {
485 let cs = core_crypto::prelude::CiphersuiteName::try_from(*c)?;
486 acc.0.push(cs);
487 Ok(acc)
488 })
489 }
490});
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Record)]
493pub struct CrlRegistration {
495 pub dirty: bool,
497 pub expiration: Option<u64>,
499}
500
501impl From<core_crypto::e2e_identity::CrlRegistration> for CrlRegistration {
502 fn from(value: core_crypto::e2e_identity::CrlRegistration) -> Self {
503 Self {
504 dirty: value.dirty,
505 expiration: value.expiration,
506 }
507 }
508}
509
510#[derive(Debug, Clone, uniffi::Record)]
511pub struct ProteusAutoPrekeyBundle {
512 pub id: u16,
513 pub pkb: Vec<u8>,
514}
515
516#[derive(Debug, uniffi::Record)]
517pub struct WelcomeBundle {
519 pub id: ConversationId,
520 pub crl_new_distribution_points: Option<Vec<String>>,
521}
522
523impl From<core_crypto::prelude::WelcomeBundle> for WelcomeBundle {
524 fn from(w: core_crypto::prelude::WelcomeBundle) -> Self {
525 Self {
526 id: w.id,
527 crl_new_distribution_points: w.crl_new_distribution_points.into(),
528 }
529 }
530}
531
532#[derive(Debug, uniffi::Record)]
533pub struct CommitBundle {
534 pub welcome: Option<Vec<u8>>,
535 pub commit: Vec<u8>,
536 pub group_info: GroupInfoBundle,
537}
538
539impl TryFrom<MlsCommitBundle> for CommitBundle {
540 type Error = CoreCryptoError;
541
542 fn try_from(msg: MlsCommitBundle) -> Result<Self, Self::Error> {
543 let (welcome, commit, group_info) = msg.to_bytes_triple()?;
544 Ok(Self {
545 welcome,
546 commit,
547 group_info: group_info.into(),
548 })
549 }
550}
551
552#[derive(Debug, Clone, Copy, uniffi::Enum)]
553#[repr(u8)]
554pub enum MlsGroupInfoEncryptionType {
555 Plaintext = 1,
557 JweEncrypted = 2,
559}
560
561impl From<core_crypto::prelude::MlsGroupInfoEncryptionType> for MlsGroupInfoEncryptionType {
562 fn from(value: core_crypto::prelude::MlsGroupInfoEncryptionType) -> Self {
563 match value {
564 core_crypto::prelude::MlsGroupInfoEncryptionType::Plaintext => Self::Plaintext,
565 core_crypto::prelude::MlsGroupInfoEncryptionType::JweEncrypted => Self::JweEncrypted,
566 }
567 }
568}
569
570impl From<MlsGroupInfoEncryptionType> for core_crypto::prelude::MlsGroupInfoEncryptionType {
571 fn from(value: MlsGroupInfoEncryptionType) -> Self {
572 match value {
573 MlsGroupInfoEncryptionType::Plaintext => Self::Plaintext,
574 MlsGroupInfoEncryptionType::JweEncrypted => Self::JweEncrypted,
575 }
576 }
577}
578
579#[derive(Debug, Clone, Copy, uniffi::Enum)]
580#[repr(u8)]
581pub enum MlsRatchetTreeType {
582 Full = 1,
584 Delta = 2,
587 ByRef = 3,
588}
589
590impl From<core_crypto::prelude::MlsRatchetTreeType> for MlsRatchetTreeType {
591 fn from(value: core_crypto::prelude::MlsRatchetTreeType) -> Self {
592 match value {
593 core_crypto::prelude::MlsRatchetTreeType::Full => Self::Full,
594 core_crypto::prelude::MlsRatchetTreeType::Delta => Self::Delta,
595 core_crypto::prelude::MlsRatchetTreeType::ByRef => Self::ByRef,
596 }
597 }
598}
599
600impl From<MlsRatchetTreeType> for core_crypto::prelude::MlsRatchetTreeType {
601 fn from(value: MlsRatchetTreeType) -> Self {
602 match value {
603 MlsRatchetTreeType::Full => Self::Full,
604 MlsRatchetTreeType::Delta => Self::Delta,
605 MlsRatchetTreeType::ByRef => Self::ByRef,
606 }
607 }
608}
609
610#[derive(Debug, Clone, uniffi::Record)]
611pub struct GroupInfoBundle {
612 pub encryption_type: MlsGroupInfoEncryptionType,
613 pub ratchet_tree_type: MlsRatchetTreeType,
614 pub payload: Vec<u8>,
615}
616
617impl From<MlsGroupInfoBundle> for GroupInfoBundle {
618 fn from(gi: MlsGroupInfoBundle) -> Self {
619 Self {
620 encryption_type: gi.encryption_type.into(),
621 ratchet_tree_type: gi.ratchet_tree_type.into(),
622 payload: gi.payload.bytes(),
623 }
624 }
625}
626
627#[derive(Debug, uniffi::Record)]
628pub struct ProposalBundle {
629 pub proposal: Vec<u8>,
630 pub proposal_ref: Vec<u8>,
631 pub crl_new_distribution_points: Option<Vec<String>>,
632}
633
634impl TryFrom<MlsProposalBundle> for ProposalBundle {
635 type Error = CoreCryptoError;
636
637 fn try_from(msg: MlsProposalBundle) -> Result<Self, Self::Error> {
638 let (proposal, proposal_ref, crl_new_distribution_points) = msg.to_bytes()?;
639 Ok(Self {
640 proposal,
641 proposal_ref,
642 crl_new_distribution_points: crl_new_distribution_points.into(),
643 })
644 }
645}
646
647#[derive(Debug, uniffi::Record)]
648pub struct DecryptedMessage {
650 pub message: Option<Vec<u8>>,
651 pub proposals: Vec<ProposalBundle>,
652 pub is_active: bool,
653 pub commit_delay: Option<u64>,
654 pub sender_client_id: Option<ClientId>,
655 pub has_epoch_changed: bool,
656 pub identity: WireIdentity,
657 pub buffered_messages: Option<Vec<BufferedDecryptedMessage>>,
658 pub crl_new_distribution_points: Option<Vec<String>>,
659}
660
661#[derive(Debug, uniffi::Record)]
662pub struct BufferedDecryptedMessage {
664 pub message: Option<Vec<u8>>,
665 pub proposals: Vec<ProposalBundle>,
666 pub is_active: bool,
667 pub commit_delay: Option<u64>,
668 pub sender_client_id: Option<ClientId>,
669 #[deprecated = "This member will be removed in the future. Prefer using the `EpochObserver` interface."]
671 pub has_epoch_changed: bool,
672 pub identity: WireIdentity,
673 pub crl_new_distribution_points: Option<Vec<String>>,
674}
675
676impl TryFrom<MlsConversationDecryptMessage> for DecryptedMessage {
677 type Error = CoreCryptoError;
678
679 fn try_from(from: MlsConversationDecryptMessage) -> Result<Self, Self::Error> {
680 let proposals = from
681 .proposals
682 .into_iter()
683 .map(ProposalBundle::try_from)
684 .collect::<CoreCryptoResult<Vec<_>>>()?;
685
686 let buffered_messages = from
687 .buffered_messages
688 .map(|bm| {
689 bm.into_iter()
690 .map(TryInto::try_into)
691 .collect::<CoreCryptoResult<Vec<_>>>()
692 })
693 .transpose()?;
694
695 #[expect(deprecated)]
696 Ok(Self {
697 message: from.app_msg,
698 proposals,
699 is_active: from.is_active,
700 commit_delay: from.delay,
701 sender_client_id: from.sender_client_id.map(ClientId),
702 has_epoch_changed: from.has_epoch_changed,
703 identity: from.identity.into(),
704 buffered_messages,
705 crl_new_distribution_points: from.crl_new_distribution_points.into(),
706 })
707 }
708}
709
710impl TryFrom<MlsBufferedConversationDecryptMessage> for BufferedDecryptedMessage {
711 type Error = CoreCryptoError;
712
713 fn try_from(from: MlsBufferedConversationDecryptMessage) -> Result<Self, Self::Error> {
714 let proposals = from
715 .proposals
716 .into_iter()
717 .map(ProposalBundle::try_from)
718 .collect::<CoreCryptoResult<Vec<_>>>()?;
719
720 #[expect(deprecated)]
721 Ok(Self {
722 message: from.app_msg,
723 proposals,
724 is_active: from.is_active,
725 commit_delay: from.delay,
726 sender_client_id: from.sender_client_id.map(ClientId),
727 has_epoch_changed: from.has_epoch_changed,
728 identity: from.identity.into(),
729 crl_new_distribution_points: from.crl_new_distribution_points.into(),
730 })
731 }
732}
733
734#[derive(Debug, uniffi::Record)]
735pub struct WireIdentity {
737 pub client_id: String,
738 pub status: DeviceStatus,
739 pub thumbprint: String,
740 pub credential_type: MlsCredentialType,
741 pub x509_identity: Option<X509Identity>,
742}
743
744impl From<core_crypto::prelude::WireIdentity> for WireIdentity {
745 fn from(i: core_crypto::prelude::WireIdentity) -> Self {
746 Self {
747 client_id: i.client_id,
748 status: i.status.into(),
749 thumbprint: i.thumbprint,
750 credential_type: i.credential_type.into(),
751 x509_identity: i.x509_identity.map(Into::into),
752 }
753 }
754}
755
756#[derive(Debug, Copy, Clone, Eq, PartialEq, uniffi::Enum)]
757#[repr(u8)]
758pub enum DeviceStatus {
759 Valid = 1,
761 Expired = 2,
763 Revoked = 3,
765}
766
767impl From<core_crypto::prelude::DeviceStatus> for DeviceStatus {
768 fn from(value: core_crypto::prelude::DeviceStatus) -> Self {
769 match value {
770 core_crypto::prelude::DeviceStatus::Valid => Self::Valid,
771 core_crypto::prelude::DeviceStatus::Expired => Self::Expired,
772 core_crypto::prelude::DeviceStatus::Revoked => Self::Revoked,
773 }
774 }
775}
776
777#[derive(Debug, uniffi::Record)]
778pub struct X509Identity {
780 pub handle: String,
781 pub display_name: String,
782 pub domain: String,
783 pub certificate: String,
784 pub serial_number: String,
785 pub not_before: u64,
786 pub not_after: u64,
787}
788
789impl From<core_crypto::prelude::X509Identity> for X509Identity {
790 fn from(i: core_crypto::prelude::X509Identity) -> Self {
791 Self {
792 handle: i.handle,
793 display_name: i.display_name,
794 domain: i.domain,
795 certificate: i.certificate,
796 serial_number: i.serial_number,
797 not_before: i.not_before,
798 not_after: i.not_after,
799 }
800 }
801}
802
803#[derive(Debug, Clone, uniffi::Record)]
804pub struct ConversationConfiguration {
806 pub ciphersuite: Ciphersuite,
807 pub external_senders: Vec<Vec<u8>>,
808 pub custom: CustomConfiguration,
809}
810
811#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, uniffi::Enum)]
812#[repr(u8)]
813pub enum MlsWirePolicy {
814 #[default]
816 Plaintext = 1,
817 Ciphertext = 2,
819}
820
821impl From<core_crypto::prelude::MlsWirePolicy> for MlsWirePolicy {
822 fn from(value: core_crypto::prelude::MlsWirePolicy) -> Self {
823 match value {
824 core_crypto::prelude::MlsWirePolicy::Plaintext => Self::Plaintext,
825 core_crypto::prelude::MlsWirePolicy::Ciphertext => Self::Ciphertext,
826 }
827 }
828}
829
830impl From<MlsWirePolicy> for core_crypto::prelude::MlsWirePolicy {
831 fn from(value: MlsWirePolicy) -> core_crypto::prelude::MlsWirePolicy {
832 match value {
833 MlsWirePolicy::Plaintext => core_crypto::prelude::MlsWirePolicy::Plaintext,
834 MlsWirePolicy::Ciphertext => core_crypto::prelude::MlsWirePolicy::Ciphertext,
835 }
836 }
837}
838
839#[derive(Debug, Clone, uniffi::Record)]
840pub struct CustomConfiguration {
842 pub key_rotation_span: Option<std::time::Duration>,
843 pub wire_policy: Option<MlsWirePolicy>,
844}
845
846impl From<CustomConfiguration> for MlsCustomConfiguration {
847 fn from(cfg: CustomConfiguration) -> Self {
848 Self {
849 key_rotation_span: cfg.key_rotation_span,
850 wire_policy: cfg.wire_policy.unwrap_or_default().into(),
851 ..Default::default()
852 }
853 }
854}
855
856#[derive(Debug, Clone, uniffi::Record)]
857pub struct E2eiDumpedPkiEnv {
859 pub root_ca: String,
860 pub intermediates: Vec<String>,
861 pub crls: Vec<String>,
862}
863
864impl From<core_crypto::e2e_identity::E2eiDumpedPkiEnv> for E2eiDumpedPkiEnv {
865 fn from(value: core_crypto::e2e_identity::E2eiDumpedPkiEnv) -> Self {
866 Self {
867 root_ca: value.root_ca,
868 intermediates: value.intermediates,
869 crls: value.crls,
870 }
871 }
872}
873
874#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, uniffi::Enum)]
875#[repr(u8)]
876pub enum MlsCredentialType {
877 #[default]
879 Basic = 0x01,
880 X509 = 0x02,
882}
883
884impl From<core_crypto::prelude::MlsCredentialType> for MlsCredentialType {
885 fn from(value: core_crypto::prelude::MlsCredentialType) -> Self {
886 match value {
887 core_crypto::prelude::MlsCredentialType::Basic => Self::Basic,
888 core_crypto::prelude::MlsCredentialType::X509 => Self::X509,
889 }
890 }
891}
892
893impl From<MlsCredentialType> for core_crypto::prelude::MlsCredentialType {
894 fn from(value: MlsCredentialType) -> core_crypto::prelude::MlsCredentialType {
895 match value {
896 MlsCredentialType::Basic => core_crypto::prelude::MlsCredentialType::Basic,
897 MlsCredentialType::X509 => core_crypto::prelude::MlsCredentialType::X509,
898 }
899 }
900}
901
902#[derive(Debug, Clone, PartialEq, Eq, uniffi::Enum)]
903pub enum MlsTransportResponse {
904 Success,
906 Retry,
908 Abort { reason: String },
910}
911
912impl From<MlsTransportResponse> for core_crypto::MlsTransportResponse {
913 fn from(value: MlsTransportResponse) -> Self {
914 match value {
915 MlsTransportResponse::Success => Self::Success,
916 MlsTransportResponse::Retry => Self::Retry,
917 MlsTransportResponse::Abort { reason } => Self::Abort { reason },
918 }
919 }
920}
921
922impl From<core_crypto::MlsTransportResponse> for MlsTransportResponse {
923 fn from(value: core_crypto::MlsTransportResponse) -> Self {
924 match value {
925 core_crypto::MlsTransportResponse::Success => Self::Success,
926 core_crypto::MlsTransportResponse::Retry => Self::Retry,
927 core_crypto::MlsTransportResponse::Abort { reason } => Self::Abort { reason },
928 }
929 }
930}
931
932#[derive(Debug)]
933struct MlsTransportWrapper(Arc<dyn MlsTransport>);
934
935#[async_trait::async_trait]
936impl core_crypto::prelude::MlsTransport for MlsTransportWrapper {
937 async fn send_commit_bundle(
938 &self,
939 commit_bundle: MlsCommitBundle,
940 ) -> core_crypto::Result<core_crypto::MlsTransportResponse> {
941 let commit_bundle = CommitBundle::try_from(commit_bundle)
942 .map_err(|e| core_crypto::Error::ErrorDuringMlsTransport(e.to_string()))?;
943 Ok(self.0.send_commit_bundle(commit_bundle).await.into())
944 }
945
946 async fn send_message(&self, mls_message: Vec<u8>) -> core_crypto::Result<core_crypto::MlsTransportResponse> {
947 Ok(self.0.send_message(mls_message).await.into())
948 }
949}
950
951#[uniffi::export(with_foreign)]
954#[async_trait::async_trait]
955pub trait MlsTransport: std::fmt::Debug + Send + Sync {
956 async fn send_commit_bundle(&self, commit_bundle: CommitBundle) -> MlsTransportResponse;
957 async fn send_message(&self, mls_message: Vec<u8>) -> MlsTransportResponse;
958}
959
960static INIT_LOGGER: Once = Once::new();
961static LOGGER: LazyLock<ReloadLog<CoreCryptoLoggerWrapper>> = LazyLock::new(|| {
962 ReloadLog::new(CoreCryptoLoggerWrapper {
963 logger: Arc::new(DummyLogger {}),
964 })
965});
966
967#[uniffi::export]
971pub fn set_logger(logger: Arc<dyn CoreCryptoLogger>, level: CoreCryptoLogLevel) {
972 set_logger_only(logger);
973 set_max_log_level(level);
974}
975
976#[uniffi::export]
978pub fn set_logger_only(logger: Arc<dyn CoreCryptoLogger>) {
979 LOGGER.handle().replace(CoreCryptoLoggerWrapper { logger }).unwrap();
981
982 INIT_LOGGER.call_once(|| {
983 log::set_logger(LOGGER.deref()).unwrap();
984 log::set_max_level(LevelFilter::Warn);
985 });
986}
987
988#[uniffi::export]
990pub fn set_max_log_level(level: CoreCryptoLogLevel) {
991 log::set_max_level(level.into());
992}
993
994#[uniffi::export(with_foreign)]
996pub trait CoreCryptoLogger: std::fmt::Debug + Send + Sync {
997 fn log(&self, level: CoreCryptoLogLevel, message: String, context: Option<String>);
1000}
1001
1002struct KeyValueVisitor<'kvs>(BTreeMap<Key<'kvs>, Value<'kvs>>);
1003
1004impl<'kvs> VisitSource<'kvs> for KeyValueVisitor<'kvs> {
1005 #[inline]
1006 fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> {
1007 self.0.insert(key, value);
1008 Ok(())
1009 }
1010}
1011#[derive(Debug)]
1012struct DummyLogger {}
1013
1014impl CoreCryptoLogger for DummyLogger {
1015 #[allow(unused_variables)]
1016 fn log(&self, level: CoreCryptoLogLevel, json_msg: String, context: Option<String>) {}
1017}
1018
1019#[derive(Clone)]
1020struct CoreCryptoLoggerWrapper {
1021 logger: std::sync::Arc<dyn CoreCryptoLogger>,
1022}
1023
1024impl CoreCryptoLoggerWrapper {
1025 fn adjusted_log_level(&self, metadata: &Metadata) -> Level {
1026 match (metadata.level(), metadata.target()) {
1027 (level, "refinery_core::traits") if level >= Level::Info => Level::Debug,
1029 (level, "refinery_core::traits::sync") if level >= Level::Info => Level::Debug,
1030 (level, _) => level,
1031 }
1032 }
1033}
1034
1035impl log::Log for CoreCryptoLoggerWrapper {
1036 fn enabled(&self, metadata: &Metadata) -> bool {
1037 log::max_level() >= self.adjusted_log_level(metadata)
1038 }
1039
1040 fn log(&self, record: &Record) {
1041 let kvs = record.key_values();
1042 let mut visitor = KeyValueVisitor(BTreeMap::new());
1043 let _ = kvs.visit(&mut visitor);
1044
1045 if !self.enabled(record.metadata()) {
1046 return;
1047 }
1048
1049 let message = format!("{}", record.args());
1050 let context = serde_json::to_string(&visitor.0).ok();
1051 self.logger.log(
1052 CoreCryptoLogLevel::from(&self.adjusted_log_level(record.metadata())),
1053 message,
1054 context,
1055 );
1056 }
1057
1058 fn flush(&self) {}
1059}
1060
1061#[derive(Debug, Clone, Copy, uniffi::Enum)]
1063pub enum CoreCryptoLogLevel {
1064 Off,
1065 Trace,
1066 Debug,
1067 Info,
1068 Warn,
1069 Error,
1070}
1071
1072impl From<CoreCryptoLogLevel> for LevelFilter {
1073 fn from(value: CoreCryptoLogLevel) -> LevelFilter {
1074 match value {
1075 CoreCryptoLogLevel::Off => LevelFilter::Off,
1076 CoreCryptoLogLevel::Trace => LevelFilter::Trace,
1077 CoreCryptoLogLevel::Debug => LevelFilter::Debug,
1078 CoreCryptoLogLevel::Info => LevelFilter::Info,
1079 CoreCryptoLogLevel::Warn => LevelFilter::Warn,
1080 CoreCryptoLogLevel::Error => LevelFilter::Error,
1081 }
1082 }
1083}
1084
1085impl From<&Level> for CoreCryptoLogLevel {
1086 fn from(value: &Level) -> Self {
1087 match *value {
1088 Level::Warn => CoreCryptoLogLevel::Warn,
1089 Level::Error => CoreCryptoLogLevel::Error,
1090 Level::Info => CoreCryptoLogLevel::Info,
1091 Level::Debug => CoreCryptoLogLevel::Debug,
1092 Level::Trace => CoreCryptoLogLevel::Trace,
1093 }
1094 }
1095}
1096
1097#[derive(Debug, uniffi::Object)]
1098pub struct CoreCrypto {
1099 central: core_crypto::CoreCrypto,
1100}
1101
1102#[uniffi::export]
1103pub async fn core_crypto_new(
1105 path: String,
1106 key: String,
1107 client_id: ClientId,
1108 ciphersuites: Ciphersuites,
1109 nb_key_package: Option<u32>,
1110) -> CoreCryptoResult<CoreCrypto> {
1111 CoreCrypto::new(path, key, Some(client_id), Some(ciphersuites), nb_key_package).await
1112}
1113
1114#[uniffi::export]
1115pub async fn core_crypto_deferred_init(path: String, key: String) -> CoreCryptoResult<CoreCrypto> {
1118 CoreCrypto::new(path, key, None, None, None).await
1119}
1120
1121#[allow(dead_code, unused_variables)]
1122#[uniffi::export]
1123impl CoreCrypto {
1124 #[uniffi::constructor]
1125 pub async fn new(
1126 path: String,
1127 key: String,
1128 client_id: Option<ClientId>,
1129 ciphersuites: Option<Ciphersuites>,
1130 nb_key_package: Option<u32>,
1131 ) -> CoreCryptoResult<Self> {
1132 let nb_key_package = nb_key_package
1133 .map(usize::try_from)
1134 .transpose()
1135 .map_err(CoreCryptoError::generic())?;
1136 let configuration = MlsCentralConfiguration::try_new(
1137 path,
1138 key,
1139 client_id.map(|cid| cid.0.clone()),
1140 (&ciphersuites.unwrap_or_default()).into(),
1141 None,
1142 nb_key_package,
1143 )?;
1144
1145 let central = MlsCentral::try_new(configuration).await?;
1146 let central = core_crypto::CoreCrypto::from(central);
1147
1148 Ok(CoreCrypto { central })
1149 }
1150
1151 pub async fn provide_transport(&self, callbacks: Arc<dyn MlsTransport>) -> CoreCryptoResult<()> {
1153 self.central
1154 .provide_transport(Arc::new(MlsTransportWrapper(callbacks)))
1155 .await;
1156 Ok(())
1157 }
1158
1159 pub async fn client_public_key(
1161 &self,
1162 ciphersuite: Ciphersuite,
1163 credential_type: MlsCredentialType,
1164 ) -> CoreCryptoResult<Vec<u8>> {
1165 Ok(self
1166 .central
1167 .client_public_key(ciphersuite.into(), credential_type.into())
1168 .await?)
1169 }
1170
1171 pub async fn conversation_epoch(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<u64> {
1173 let conversation = self.central.get_raw_conversation(&conversation_id).await?;
1174 Ok(conversation.epoch().await)
1175 }
1176
1177 pub async fn conversation_ciphersuite(&self, conversation_id: &ConversationId) -> CoreCryptoResult<Ciphersuite> {
1179 let cs = self
1180 .central
1181 .get_raw_conversation(conversation_id)
1182 .await?
1183 .ciphersuite()
1184 .await;
1185 Ok(Ciphersuite::from(core_crypto::prelude::CiphersuiteName::from(cs)))
1186 }
1187
1188 pub async fn conversation_exists(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<bool> {
1190 Ok(self.central.conversation_exists(&conversation_id).await?)
1191 }
1192
1193 pub async fn random_bytes(&self, len: u32) -> CoreCryptoResult<Vec<u8>> {
1195 Ok(self
1196 .central
1197 .random_bytes(len.try_into().map_err(CoreCryptoError::generic())?)?)
1198 }
1199
1200 pub async fn reseed_rng(&self, seed: Vec<u8>) -> CoreCryptoResult<()> {
1202 let seed = EntropySeed::try_from_slice(&seed).map_err(CoreCryptoError::generic())?;
1203 self.central.reseed(Some(seed)).await?;
1204
1205 Ok(())
1206 }
1207
1208 pub async fn get_client_ids(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<Vec<ClientId>> {
1210 Ok(self
1211 .central
1212 .get_raw_conversation(&conversation_id)
1213 .await?
1214 .get_client_ids()
1215 .await
1216 .into_iter()
1217 .map(ClientId)
1218 .collect())
1219 }
1220
1221 pub async fn export_secret_key(&self, conversation_id: Vec<u8>, key_length: u32) -> CoreCryptoResult<Vec<u8>> {
1223 self.central
1224 .get_raw_conversation(&conversation_id)
1225 .await?
1226 .export_secret_key(key_length as usize)
1227 .await
1228 .map_err(Into::into)
1229 }
1230
1231 pub async fn get_external_sender(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<Vec<u8>> {
1233 Ok(self
1234 .central
1235 .get_raw_conversation(&conversation_id)
1236 .await?
1237 .get_external_sender()
1238 .await?)
1239 }
1240}
1241
1242#[derive(Debug, Copy, Clone, uniffi::Enum)]
1243#[repr(u8)]
1244pub enum E2eiConversationState {
1245 Verified = 1,
1247 NotVerified,
1249 NotEnabled,
1251}
1252
1253impl From<core_crypto::prelude::E2eiConversationState> for E2eiConversationState {
1254 fn from(value: core_crypto::prelude::E2eiConversationState) -> Self {
1255 match value {
1256 core_crypto::prelude::E2eiConversationState::Verified => Self::Verified,
1257 core_crypto::prelude::E2eiConversationState::NotVerified => Self::NotVerified,
1258 core_crypto::prelude::E2eiConversationState::NotEnabled => Self::NotEnabled,
1259 }
1260 }
1261}
1262
1263#[cfg_attr(not(feature = "proteus"), allow(unused_variables))]
1264#[uniffi::export]
1265impl CoreCrypto {
1266 pub async fn proteus_session_exists(&self, session_id: String) -> CoreCryptoResult<bool> {
1268 proteus_impl!({ Ok(self.central.proteus_session_exists(&session_id).await?) })
1269 }
1270
1271 pub fn proteus_last_resort_prekey_id(&self) -> CoreCryptoResult<u16> {
1273 proteus_impl!({ Ok(core_crypto::CoreCrypto::proteus_last_resort_prekey_id()) })
1274 }
1275
1276 pub async fn proteus_fingerprint(&self) -> CoreCryptoResult<String> {
1278 proteus_impl!({ Ok(self.central.proteus_fingerprint().await?) })
1279 }
1280
1281 pub async fn proteus_fingerprint_local(&self, session_id: String) -> CoreCryptoResult<String> {
1283 proteus_impl!({ Ok(self.central.proteus_fingerprint_local(&session_id).await?) })
1284 }
1285
1286 pub async fn proteus_fingerprint_remote(&self, session_id: String) -> CoreCryptoResult<String> {
1288 proteus_impl!({ Ok(self.central.proteus_fingerprint_remote(&session_id).await?) })
1289 }
1290
1291 pub fn proteus_fingerprint_prekeybundle(&self, prekey: Vec<u8>) -> CoreCryptoResult<String> {
1294 proteus_impl!({ Ok(core_crypto::proteus::ProteusCentral::fingerprint_prekeybundle(&prekey)?) })
1295 }
1296}
1297
1298#[allow(dead_code, unused_variables)]
1300#[uniffi::export]
1301impl CoreCrypto {
1302 pub async fn e2ei_dump_pki_env(&self) -> CoreCryptoResult<Option<E2eiDumpedPkiEnv>> {
1303 Ok(self.central.e2ei_dump_pki_env().await?.map(Into::into))
1304 }
1305
1306 pub async fn e2ei_is_pki_env_setup(&self) -> bool {
1308 self.central.e2ei_is_pki_env_setup().await
1309 }
1310
1311 pub async fn e2ei_is_enabled(&self, ciphersuite: Ciphersuite) -> CoreCryptoResult<bool> {
1313 let sc = core_crypto::prelude::MlsCiphersuite::from(core_crypto::prelude::CiphersuiteName::from(ciphersuite))
1314 .signature_algorithm();
1315 Ok(self.central.e2ei_is_enabled(sc).await?)
1316 }
1317
1318 pub async fn get_device_identities(
1320 &self,
1321 conversation_id: Vec<u8>,
1322 device_ids: Vec<ClientId>,
1323 ) -> CoreCryptoResult<Vec<WireIdentity>> {
1324 let device_ids = device_ids.into_iter().map(|cid| cid.0).collect::<Vec<_>>();
1325 Ok(self
1326 .central
1327 .get_raw_conversation(&conversation_id)
1328 .await?
1329 .get_device_identities(&device_ids[..])
1330 .await?
1331 .into_iter()
1332 .map(Into::into)
1333 .collect::<Vec<_>>())
1334 }
1335
1336 pub async fn get_user_identities(
1338 &self,
1339 conversation_id: Vec<u8>,
1340 user_ids: Vec<String>,
1341 ) -> CoreCryptoResult<HashMap<String, Vec<WireIdentity>>> {
1342 Ok(self
1343 .central
1344 .get_raw_conversation(&conversation_id)
1345 .await?
1346 .get_user_identities(&user_ids[..])
1347 .await?
1348 .into_iter()
1349 .map(|(k, v)| (k, v.into_iter().map(Into::into).collect()))
1350 .collect::<HashMap<String, Vec<WireIdentity>>>())
1351 }
1352
1353 pub async fn get_credential_in_use(
1355 &self,
1356 group_info: Vec<u8>,
1357 credential_type: MlsCredentialType,
1358 ) -> CoreCryptoResult<E2eiConversationState> {
1359 let group_info = VerifiableGroupInfo::tls_deserialize(&mut group_info.as_slice())
1360 .map_err(core_crypto::mls::conversation::Error::tls_deserialize(
1361 "verifiable group info",
1362 ))
1363 .map_err(RecursiveError::mls_conversation("deserializing veriable group info"))?;
1364 Ok(self
1365 .central
1366 .get_credential_in_use(group_info, credential_type.into())
1367 .await?
1368 .into())
1369 }
1370}
1371
1372#[derive(Debug, uniffi::Object)]
1373pub struct E2eiEnrollment(std::sync::Arc<async_lock::RwLock<core_crypto::prelude::E2eiEnrollment>>);
1375
1376#[uniffi::export]
1377impl E2eiEnrollment {
1378 pub async fn directory_response(&self, directory: Vec<u8>) -> CoreCryptoResult<AcmeDirectory> {
1380 Ok(self
1381 .0
1382 .write()
1383 .await
1384 .directory_response(directory)
1385 .map(AcmeDirectory::from)?)
1386 }
1387
1388 pub async fn new_account_request(&self, previous_nonce: String) -> CoreCryptoResult<Vec<u8>> {
1390 Ok(self.0.read().await.new_account_request(previous_nonce)?)
1391 }
1392
1393 pub async fn new_account_response(&self, account: Vec<u8>) -> CoreCryptoResult<()> {
1395 Ok(self.0.write().await.new_account_response(account)?)
1396 }
1397
1398 #[allow(clippy::too_many_arguments)]
1400 pub async fn new_order_request(&self, previous_nonce: String) -> CoreCryptoResult<Vec<u8>> {
1401 Ok(self.0.read().await.new_order_request(previous_nonce)?)
1402 }
1403
1404 pub async fn new_order_response(&self, order: Vec<u8>) -> CoreCryptoResult<NewAcmeOrder> {
1406 Ok(self.0.read().await.new_order_response(order)?.into())
1407 }
1408
1409 pub async fn new_authz_request(&self, url: String, previous_nonce: String) -> CoreCryptoResult<Vec<u8>> {
1411 Ok(self.0.read().await.new_authz_request(url, previous_nonce)?)
1412 }
1413
1414 pub async fn new_authz_response(&self, authz: Vec<u8>) -> CoreCryptoResult<NewAcmeAuthz> {
1416 Ok(self.0.write().await.new_authz_response(authz)?.into())
1417 }
1418
1419 #[allow(clippy::too_many_arguments)]
1420 pub async fn create_dpop_token(&self, expiry_secs: u32, backend_nonce: String) -> CoreCryptoResult<String> {
1422 Ok(self.0.read().await.create_dpop_token(expiry_secs, backend_nonce)?)
1423 }
1424
1425 pub async fn new_dpop_challenge_request(
1427 &self,
1428 access_token: String,
1429 previous_nonce: String,
1430 ) -> CoreCryptoResult<Vec<u8>> {
1431 Ok(self
1432 .0
1433 .read()
1434 .await
1435 .new_dpop_challenge_request(access_token, previous_nonce)?)
1436 }
1437
1438 pub async fn new_dpop_challenge_response(&self, challenge: Vec<u8>) -> CoreCryptoResult<()> {
1440 Ok(self.0.read().await.new_dpop_challenge_response(challenge)?)
1441 }
1442
1443 pub async fn new_oidc_challenge_request(
1445 &self,
1446 id_token: String,
1447 refresh_token: String,
1448 previous_nonce: String,
1449 ) -> CoreCryptoResult<Vec<u8>> {
1450 Ok(self
1451 .0
1452 .write()
1453 .await
1454 .new_oidc_challenge_request(id_token, refresh_token, previous_nonce)?)
1455 }
1456
1457 pub async fn context_new_oidc_challenge_response(
1459 &self,
1460 cc: std::sync::Arc<CoreCryptoContext>,
1461 challenge: Vec<u8>,
1462 ) -> CoreCryptoResult<()> {
1463 self.0
1464 .write()
1465 .await
1466 .new_oidc_challenge_response(&cc.context.mls_provider().await?, challenge)
1467 .await?;
1468 Ok(())
1469 }
1470
1471 pub async fn check_order_request(&self, order_url: String, previous_nonce: String) -> CoreCryptoResult<Vec<u8>> {
1473 Ok(self.0.read().await.check_order_request(order_url, previous_nonce)?)
1474 }
1475
1476 pub async fn check_order_response(&self, order: Vec<u8>) -> CoreCryptoResult<String> {
1478 Ok(self.0.write().await.check_order_response(order)?)
1479 }
1480
1481 pub async fn finalize_request(&self, previous_nonce: String) -> CoreCryptoResult<Vec<u8>> {
1483 Ok(self.0.write().await.finalize_request(previous_nonce)?)
1484 }
1485
1486 pub async fn finalize_response(&self, finalize: Vec<u8>) -> CoreCryptoResult<String> {
1488 Ok(self.0.write().await.finalize_response(finalize)?)
1489 }
1490
1491 pub async fn certificate_request(&self, previous_nonce: String) -> CoreCryptoResult<Vec<u8>> {
1493 Ok(self.0.write().await.certificate_request(previous_nonce)?)
1494 }
1495
1496 pub async fn get_refresh_token(&self) -> CoreCryptoResult<String> {
1498 Ok(self.0.read().await.get_refresh_token().map(Into::into)?)
1499 }
1500}
1501
1502#[derive(Debug, uniffi::Record)]
1503pub struct AcmeDirectory {
1505 pub new_nonce: String,
1506 pub new_account: String,
1507 pub new_order: String,
1508 pub revoke_cert: String,
1509}
1510
1511impl From<core_crypto::prelude::E2eiAcmeDirectory> for AcmeDirectory {
1512 fn from(directory: core_crypto::prelude::E2eiAcmeDirectory) -> Self {
1513 Self {
1514 new_nonce: directory.new_nonce,
1515 new_account: directory.new_account,
1516 new_order: directory.new_order,
1517 revoke_cert: directory.revoke_cert,
1518 }
1519 }
1520}
1521
1522impl From<AcmeDirectory> for core_crypto::prelude::E2eiAcmeDirectory {
1523 fn from(directory: AcmeDirectory) -> Self {
1524 Self {
1525 new_nonce: directory.new_nonce,
1526 new_account: directory.new_account,
1527 new_order: directory.new_order,
1528 revoke_cert: directory.revoke_cert,
1529 }
1530 }
1531}
1532
1533#[derive(Debug, uniffi::Record)]
1534pub struct NewAcmeOrder {
1536 pub delegate: Vec<u8>,
1537 pub authorizations: Vec<String>,
1538}
1539
1540impl From<core_crypto::prelude::E2eiNewAcmeOrder> for NewAcmeOrder {
1541 fn from(new_order: core_crypto::prelude::E2eiNewAcmeOrder) -> Self {
1542 Self {
1543 delegate: new_order.delegate,
1544 authorizations: new_order.authorizations,
1545 }
1546 }
1547}
1548
1549impl From<NewAcmeOrder> for core_crypto::prelude::E2eiNewAcmeOrder {
1550 fn from(new_order: NewAcmeOrder) -> Self {
1551 Self {
1552 delegate: new_order.delegate,
1553 authorizations: new_order.authorizations,
1554 }
1555 }
1556}
1557
1558#[derive(Debug, uniffi::Record)]
1559pub struct NewAcmeAuthz {
1561 pub identifier: String,
1562 pub keyauth: Option<String>,
1563 pub challenge: AcmeChallenge,
1564}
1565
1566impl From<core_crypto::prelude::E2eiNewAcmeAuthz> for NewAcmeAuthz {
1567 fn from(new_authz: core_crypto::prelude::E2eiNewAcmeAuthz) -> Self {
1568 Self {
1569 identifier: new_authz.identifier,
1570 keyauth: new_authz.keyauth,
1571 challenge: new_authz.challenge.into(),
1572 }
1573 }
1574}
1575
1576impl From<NewAcmeAuthz> for core_crypto::prelude::E2eiNewAcmeAuthz {
1577 fn from(new_authz: NewAcmeAuthz) -> Self {
1578 Self {
1579 identifier: new_authz.identifier,
1580 keyauth: new_authz.keyauth,
1581 challenge: new_authz.challenge.into(),
1582 }
1583 }
1584}
1585
1586#[derive(Debug, uniffi::Record)]
1587pub struct AcmeChallenge {
1589 pub delegate: Vec<u8>,
1590 pub url: String,
1591 pub target: String,
1592}
1593
1594impl From<core_crypto::prelude::E2eiAcmeChallenge> for AcmeChallenge {
1595 fn from(chall: core_crypto::prelude::E2eiAcmeChallenge) -> Self {
1596 Self {
1597 delegate: chall.delegate,
1598 url: chall.url,
1599 target: chall.target,
1600 }
1601 }
1602}
1603
1604impl From<AcmeChallenge> for core_crypto::prelude::E2eiAcmeChallenge {
1605 fn from(chall: AcmeChallenge) -> Self {
1606 Self {
1607 delegate: chall.delegate,
1608 url: chall.url,
1609 target: chall.target,
1610 }
1611 }
1612}
1613
1614#[cfg(test)]
1615mod tests {
1616 use super::*;
1617 use core_crypto::LeafError;
1618 #[test]
1619 fn test_error_mapping() {
1620 let duplicate_message_error = RecursiveError::mls_conversation("test duplicate message error")(
1621 core_crypto::mls::conversation::Error::DuplicateMessage,
1622 );
1623 let mapped_error = CoreCryptoError::from(duplicate_message_error);
1624 assert!(matches!(mapped_error, CoreCryptoError::Mls(MlsError::DuplicateMessage)));
1625
1626 let conversation_exists_error = RecursiveError::mls_conversation("test conversation exists error")(
1627 core_crypto::mls::conversation::Error::Leaf(LeafError::ConversationAlreadyExists(
1628 "test conversation id".into(),
1629 )),
1630 );
1631 let mapped_error = CoreCryptoError::from(conversation_exists_error);
1632 assert!(matches!(
1633 mapped_error,
1634 CoreCryptoError::Mls(MlsError::ConversationAlreadyExists(_))
1635 ));
1636 }
1637
1638 #[tokio::test]
1639 async fn test_error_is_logged() {
1640 testing_logger::setup();
1641 let result = CoreCrypto::new("/root/asdf".into(), "key".into(), None, None, None).await;
1644 assert!(
1645 result.is_err(),
1646 "result must be an error in order to verify that something was logged"
1647 );
1648 testing_logger::validate(|captured_logs| {
1649 assert!(
1650 captured_logs.iter().any(|log| log.level == Level::Warn
1651 && log.target == "core-crypto"
1652 && log.body.contains("returning this error across ffi")),
1653 "log message did not appear within the captured logs"
1654 )
1655 });
1656 }
1657}