core_crypto/mls/session/
e2e_identity.rs

1use openmls::{
2    prelude::{Credential, Node, SignatureScheme, group_info::VerifiableGroupInfo},
3    treesync::RatchetTree,
4};
5use wire_e2e_identity::prelude::WireIdentityReader as _;
6
7use crate::{
8    MlsError,
9    mls::session::CredentialExt as _,
10    prelude::{E2eiConversationState, MlsCiphersuite, MlsCredentialType},
11};
12use openmls_traits::OpenMlsCryptoProvider as _;
13
14use super::{Error, Result, Session};
15
16impl Session {
17    /// Returns whether the E2EI PKI environment is setup (i.e. Root CA, Intermediates, CRLs)
18    pub async fn e2ei_is_pki_env_setup(&self) -> bool {
19        self.crypto_provider.is_pki_env_setup().await
20    }
21
22    /// Returns true when end-to-end-identity is enabled for the given SignatureScheme
23    pub async fn e2ei_is_enabled(&self, signature_scheme: SignatureScheme) -> Result<bool> {
24        let x509_result = self
25            .find_most_recent_credential_bundle(signature_scheme, MlsCredentialType::X509)
26            .await;
27        match x509_result {
28            Err(Error::CredentialNotFound(MlsCredentialType::X509)) => {
29                self.find_most_recent_credential_bundle(signature_scheme, MlsCredentialType::Basic)
30                    .await?;
31                Ok(false)
32            }
33            Err(e) => Err(e),
34            Ok(_) => Ok(true),
35        }
36    }
37
38    /// Verifies a Group state before joining it
39    pub async fn e2ei_verify_group_state(&self, group_info: VerifiableGroupInfo) -> Result<E2eiConversationState> {
40        self.crypto_provider
41            .authentication_service()
42            .refresh_time_of_interest()
43            .await;
44
45        let cs = group_info.ciphersuite().into();
46
47        let is_sender = true; // verify the ratchet tree as sender to turn on hardened verification
48        let Ok(rt) = group_info.take_ratchet_tree(&self.crypto_provider, is_sender).await else {
49            return Ok(E2eiConversationState::NotVerified);
50        };
51
52        let credentials = rt.iter().filter_map(|n| match n {
53            Some(Node::LeafNode(ln)) => Some(ln.credential()),
54            _ => None,
55        });
56
57        Ok(Self::compute_conversation_state(
58            cs,
59            credentials,
60            MlsCredentialType::X509,
61            self.crypto_provider.authentication_service().borrow().await.as_ref(),
62        )
63        .await)
64    }
65
66    /// Gets the e2ei conversation state from a `GroupInfo`. Useful to check if the group has e2ei
67    /// turned on or not before joining it.
68    pub async fn get_credential_in_use(
69        &self,
70        group_info: VerifiableGroupInfo,
71        credential_type: MlsCredentialType,
72    ) -> Result<E2eiConversationState> {
73        let cs = group_info.ciphersuite().into();
74        // Not verifying the supplied the GroupInfo here could let attackers lure the clients about
75        // the e2ei state of a conversation and as a consequence degrade this conversation for all
76        // participants once joining it.
77        // This 👇 verifies the GroupInfo and the RatchetTree btw
78        let rt = group_info
79            .take_ratchet_tree(&self.crypto_provider, false)
80            .await
81            .map_err(MlsError::wrap("taking ratchet tree"))?;
82        Self::get_credential_in_use_in_ratchet_tree(
83            cs,
84            rt,
85            credential_type,
86            self.crypto_provider.authentication_service().borrow().await.as_ref(),
87        )
88        .await
89    }
90    pub(crate) async fn get_credential_in_use_in_ratchet_tree(
91        ciphersuite: MlsCiphersuite,
92        ratchet_tree: RatchetTree,
93        credential_type: MlsCredentialType,
94        env: Option<&wire_e2e_identity::prelude::x509::revocation::PkiEnvironment>,
95    ) -> Result<E2eiConversationState> {
96        let credentials = ratchet_tree.iter().filter_map(|n| match n {
97            Some(Node::LeafNode(ln)) => Some(ln.credential()),
98            _ => None,
99        });
100        Ok(Self::compute_conversation_state(ciphersuite, credentials, credential_type, env).await)
101    }
102
103    /// _credential_type will be used in the future to get the usage of VC Credentials, even Basics one.
104    /// Right now though, we do not need anything other than X509 so let's keep things simple.
105    pub(crate) async fn compute_conversation_state<'a>(
106        ciphersuite: MlsCiphersuite,
107        credentials: impl Iterator<Item = &'a Credential>,
108        _credential_type: MlsCredentialType,
109        env: Option<&wire_e2e_identity::prelude::x509::revocation::PkiEnvironment>,
110    ) -> E2eiConversationState {
111        let mut is_e2ei = false;
112        let mut state = E2eiConversationState::Verified;
113
114        for credential in credentials {
115            let Ok(Some(cert)) = credential.parse_leaf_cert() else {
116                state = E2eiConversationState::NotVerified;
117                if is_e2ei {
118                    break;
119                }
120                continue;
121            };
122
123            is_e2ei = true;
124
125            let invalid_identity = cert.extract_identity(env, ciphersuite.e2ei_hash_alg()).is_err();
126
127            use openmls_x509_credential::X509Ext as _;
128            let is_time_valid = cert.is_time_valid().unwrap_or(false);
129            let is_time_invalid = !is_time_valid;
130            let is_revoked_or_invalid = env
131                .map(|e| e.validate_cert_and_revocation(&cert).is_err())
132                .unwrap_or(false);
133
134            let is_invalid = invalid_identity || is_time_invalid || is_revoked_or_invalid;
135            if is_invalid {
136                state = E2eiConversationState::NotVerified;
137                break;
138            }
139        }
140
141        if is_e2ei {
142            state
143        } else {
144            E2eiConversationState::NotEnabled
145        }
146    }
147}