core_crypto_ffi/generic/
mod.rs

1// Wire
2// Copyright (C) 2022 Wire Swiss GmbH
3
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see http://www.gnu.org/licenses/.
16
17use 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)]
55/// Metadata describing the conditions of the build of this software.
56pub struct BuildMetadata {
57    /// Build Timestamp
58    pub timestamp: String,
59    /// Whether this build was in Debug mode (true) or Release mode (false)
60    pub cargo_debug: String,
61    /// Features enabled for this build
62    pub cargo_features: String,
63    /// Optimization level
64    pub opt_level: String,
65    /// Build target triple
66    pub target_triple: String,
67    /// Git branch
68    pub git_branch: String,
69    /// Output of `git describe`
70    pub git_describe: String,
71    /// Hash of current git commit
72    pub git_sha: String,
73    /// `true` when the source code differed from the commit at the most recent git hash
74    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    /// This happens when the DS cannot flag KeyPackages as claimed or not. It this scenario, a client
119    /// requests their old KeyPackages to be deleted but one has already been claimed by another client to create a Welcome.
120    /// In that case the only solution is that the client receiving such a Welcome tries to join the group
121    /// with an External Commit instead
122    #[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    /// Message rejected by the delivery service
127    #[error("Message rejected by the delivery service. Reason: {reason}")]
128    MessageRejected {
129        /// Why was the message rejected by the delivery service?
130        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
211/// Prepare and dispatch a log message reporting this error.
212///
213/// We want to ensure consistent logging every time we pass a log message across the FFI boundary,
214/// as we cannot guarantee the method, format, or existence of error logging once the result crosses.
215/// Unfortunately, as there is no single point at which we convert internal errors to trans-ffi
216/// errors, we need to extract the logging procedure and ensure it's called at each relevant point.
217///
218/// This has the further disadvantage that we have very little context information at the point of
219/// logging. We'll try this out for now anyway; if it turns out that we need to add more tracing
220/// in the future, we can figure out our techniques then.
221fn log_error(error: &dyn std::error::Error) {
222    // we exclude the original error message from the chain
223    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    // even though there exists a `:err` formatter, it only captures the top-level
235    // message from the error, so it's still worth building our own inner error formatter
236    // and using serde here
237    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        // check if the innermost error is any kind of e2e error
245        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        // What now? We only really care about the innermost variants, not the error stack, but that produces
258        // an arbitrary set of types. We can't match against that!
259        //
260        // Or at least, not without the power of macros. We can use them to match against heterogenous types.
261
262        /// Like [`matches!`], but with an out expression which can reference items captured by the pattern.
263        ///
264        /// Hopefully only ever use this in conjunction with `interior_matches!`, because for most sane
265        /// circumstances, `if let` is the better design pattern.
266        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        /// This is moderately horrific and we hopefully will not require it anywhere else, but
276        /// it solves a real problem here: how do we match against the innermost error variants,
277        /// when we have a heterogenous set of types to match against?
278        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            // The internal name is what we want, but renaming the external variant is a breaking change.
308            // Since we're re-designing the `BufferedMessage` errors soon, it's not worth producing
309            // an additional breaking change until then, so the names are inconsistent.
310            core_crypto::mls::conversation::Error::BufferedForPendingConversation => MlsError::UnmergedPendingGroup.into(),
311            ||=> MlsError::Other(error.innermost_error_message()).into(),
312        })
313    }
314}
315
316// This implementation is intended to be temporary; we're going to be completely restructuring the way we handle
317// errors in `core-crypto` soon. We can replace this with better error patterns when we do.
318//
319// Certain error mappings could apply to both MLS and Proteus. In all such cases, we map them to the MLS variant.
320// When we redesign the errors in `core-crypto`, these ambiguities should disappear anyway.
321impl From<core_crypto::Error> for CoreCryptoError {
322    fn from(error: core_crypto::Error) -> Self {
323        log_error(&error);
324
325        // we can take care of the _simple_ error-mapping up here.
326        #[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
349/// We can't do a generic `impl<E: ToRecursiveError> From<E> for CoreCryptoError`
350/// because that has the potential to cause breaking conflicts later on: what if
351/// core-crypto later did `impl ToRecursiveError for core_crypto::Error`? That would
352/// cause a duplicate `From` impl.
353///
354/// Instead, we explicitly specify every variant which can be converted to a
355/// `CoreCryptoError`, and implement its `From` block directly.
356macro_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    /// DH KEM x25519 | AES-GCM 128 | SHA2-256 | Ed25519
412    MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 = 0x0001,
413    /// DH KEM P256 | AES-GCM 128 | SHA2-256 | EcDSA P256
414    MLS_128_DHKEMP256_AES128GCM_SHA256_P256 = 0x0002,
415    /// DH KEM x25519 | Chacha20Poly1305 | SHA2-256 | Ed25519
416    MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 = 0x0003,
417    /// DH KEM x448 | AES-GCM 256 | SHA2-512 | Ed448
418    MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448 = 0x0004,
419    /// DH KEM P521 | AES-GCM 256 | SHA2-512 | EcDSA P521
420    MLS_256_DHKEMP521_AES256GCM_SHA512_P521 = 0x0005,
421    /// DH KEM x448 | Chacha20Poly1305 | SHA2-512 | Ed448
422    MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448 = 0x0006,
423    /// DH KEM P384 | AES-GCM 256 | SHA2-384 | EcDSA P384
424    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)]
493/// Supporting struct for CRL registration result
494pub struct CrlRegistration {
495    /// Whether this CRL modifies the old CRL (i.e. has a different revocated cert list)
496    pub dirty: bool,
497    /// Optional expiration timestamp
498    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)]
517/// see [core_crypto::prelude::MlsConversationCreationMessage]
518pub 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    /// Unencrypted `GroupInfo`
556    Plaintext = 1,
557    /// `GroupInfo` encrypted in a JWE
558    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    /// Plain old and complete `GroupInfo`
583    Full = 1,
584    /// Contains `GroupInfo` changes since previous epoch (not yet implemented)
585    /// (see [draft](https://github.com/rohan-wire/ietf-drafts/blob/main/mahy-mls-ratchet-tree-delta/draft-mahy-mls-ratchet-tree-delta.md))
586    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)]
648/// See [core_crypto::prelude::decrypt::MlsConversationDecryptMessage]
649pub 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)]
662/// because Uniffi does not support recursive structs
663pub 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.
670    #[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)]
735/// See [core_crypto::prelude::WireIdentity]
736pub 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    /// All is fine
760    Valid = 1,
761    /// The Credential's certificate is expired
762    Expired = 2,
763    /// The Credential's certificate is revoked (not implemented yet)
764    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)]
778/// See [core_crypto::prelude::X509Identity]
779pub 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)]
804/// See [core_crypto::prelude::MlsConversationConfiguration]
805pub 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    /// Handshake messages are never encrypted
815    #[default]
816    Plaintext = 1,
817    /// Handshake messages are always encrypted
818    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)]
840/// See [core_crypto::prelude::MlsCustomConfiguration]
841pub 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)]
857/// Dummy comment
858pub 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    /// Basic credential i.e. a KeyPair
878    #[default]
879    Basic = 0x01,
880    /// A x509 certificate generally obtained through e2e identity enrollment process
881    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    /// The message was accepted by the distribution service
905    Success,
906    /// A client should have consumed all incoming messages before re-trying.
907    Retry,
908    /// The message was rejected by the delivery service and there's no recovery.
909    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/// Used by core crypto to send commits or application messages to the delivery service.
952/// This trait must be implemented before calling any functions that produce commits.
953#[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/// Initializes the logger
968///
969/// NOTE: in a future  release we will remove `level` argument.
970#[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/// Initializes the logger
977#[uniffi::export]
978pub fn set_logger_only(logger: Arc<dyn CoreCryptoLogger>) {
979    // unwrapping poisoned lock error which shouldn't happen since we don't panic while replacing the logger
980    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/// Set maximum log level forwarded to the logger
989#[uniffi::export]
990pub fn set_max_log_level(level: CoreCryptoLogLevel) {
991    log::set_max_level(level.into());
992}
993
994/// This trait is used to provide a callback mechanism to hook up the rerspective platform logging system
995#[uniffi::export(with_foreign)]
996pub trait CoreCryptoLogger: std::fmt::Debug + Send + Sync {
997    /// Function to setup a hook for the logging messages. Core Crypto will call this method
998    /// whenever it needs to log a message.
999    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            // increase log level for refinery_core::traits since they are too verbose in transactions
1028            (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/// Defines the log level for a CoreCrypto
1062#[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]
1103/// See [core_crypto::mls::MlsCentral::try_new]
1104pub 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]
1115/// Similar to [core_crypto_new] but defers MLS initialization. It can be initialized later
1116/// with [CoreCryptoContext::mls_init].
1117pub 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    /// See [core_crypto::mls::MlsCentral::provide_transport]
1152    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    /// See [core_crypto::mls::MlsCentral::client_public_key]
1160    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    /// See [core_crypto::mls::conversation::ImmutableConversation::epoch]
1172    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    /// See [core_crypto::mls::conversation::ImmutableConversation::ciphersuite]
1178    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    /// See [core_crypto::mls::MlsCentral::conversation_exists]
1189    pub async fn conversation_exists(&self, conversation_id: Vec<u8>) -> CoreCryptoResult<bool> {
1190        Ok(self.central.conversation_exists(&conversation_id).await?)
1191    }
1192
1193    /// See [core_crypto::mls::MlsCentral::random_bytes]
1194    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    /// see [core_crypto::prelude::MlsCryptoProvider::reseed]
1201    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    /// See [core_crypto::mls::conversation::ImmutableConversation::get_client_ids]
1209    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    /// See [core_crypto::mls::conversation::ImmutableConversation::export_secret_key]
1222    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    /// See [core_crypto::mls::conversation::ImmutableConversation::get_external_sender]
1232    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    /// All clients have a valid E2EI certificate
1246    Verified = 1,
1247    /// Some clients are either still Basic or their certificate is expired
1248    NotVerified,
1249    /// All clients are still Basic. If all client have expired certificates, [E2eiConversationState::NotVerified] is returned.
1250    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    /// See [core_crypto::proteus::ProteusCentral::session_exists]
1267    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    /// See [core_crypto::proteus::ProteusCentral::last_resort_prekey_id]
1272    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    /// See [core_crypto::proteus::ProteusCentral::fingerprint]
1277    pub async fn proteus_fingerprint(&self) -> CoreCryptoResult<String> {
1278        proteus_impl!({ Ok(self.central.proteus_fingerprint().await?) })
1279    }
1280
1281    /// See [core_crypto::proteus::ProteusCentral::fingerprint_local]
1282    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    /// See [core_crypto::proteus::ProteusCentral::fingerprint_remote]
1287    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    /// See [core_crypto::proteus::ProteusCentral::fingerprint_prekeybundle]
1292    /// NOTE: uniffi doesn't support associated functions, so we have to have the self here
1293    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// End-to-end identity methods
1299#[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    /// See [core_crypto::mls::MlsCentral::e2ei_is_pki_env_setup]
1307    pub async fn e2ei_is_pki_env_setup(&self) -> bool {
1308        self.central.e2ei_is_pki_env_setup().await
1309    }
1310
1311    /// See [core_crypto::mls::MlsCentral::e2ei_is_enabled]
1312    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    /// See [core_crypto::mls::conversation::ConversationGuard::get_device_identities]
1319    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    /// See [core_crypto::mls::conversation::ConversationGuard::get_user_identities]
1337    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    /// See [core_crypto::mls::MlsCentral::get_credential_in_use]
1354    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)]
1373/// See [core_crypto::e2e_identity::E2eiEnrollment]
1374pub struct E2eiEnrollment(std::sync::Arc<async_lock::RwLock<core_crypto::prelude::E2eiEnrollment>>);
1375
1376#[uniffi::export]
1377impl E2eiEnrollment {
1378    /// See [core_crypto::e2e_identity::E2eiEnrollment::directory_response]
1379    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_account_request]
1389    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_account_response]
1394    pub async fn new_account_response(&self, account: Vec<u8>) -> CoreCryptoResult<()> {
1395        Ok(self.0.write().await.new_account_response(account)?)
1396    }
1397
1398    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_order_request]
1399    #[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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_order_response]
1405    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_authz_request]
1410    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_authz_response]
1415    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::create_dpop_token]
1421    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_dpop_challenge_request]
1426    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_dpop_challenge_response]
1439    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_oidc_challenge_request]
1444    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::new_oidc_challenge_response]
1458    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::check_order_request]
1472    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    /// See [core_crypto::e2e_identity::E2eiEnrollment::check_order_response]
1477    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    /// See [core_crypto::prelude::E2eiEnrollment::finalize_request]
1482    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    /// See [core_crypto::prelude::E2eiEnrollment::finalize_response]
1487    pub async fn finalize_response(&self, finalize: Vec<u8>) -> CoreCryptoResult<String> {
1488        Ok(self.0.write().await.finalize_response(finalize)?)
1489    }
1490
1491    /// See [core_crypto::prelude::E2eiEnrollment::certificate_request]
1492    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    /// See [core_crypto::prelude::E2eiEnrollment::get_refresh_token]
1497    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)]
1503/// See [core_crypto::e2e_identity::types::E2eiAcmeDirectory]
1504pub 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)]
1534/// See [core_crypto::e2e_identity::types::E2eiNewAcmeOrder]
1535pub 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)]
1559/// See [core_crypto::e2e_identity::types::E2eiNewAcmeAuthz]
1560pub 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)]
1587/// See [core_crypto::e2e_identity::types::E2eiAcmeChallenge]
1588pub 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        // we shouldn't be able to create a SQLite DB in `/root` unless we are running this test as root
1642        // Don't do that!
1643        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}