Skip to main content

core_crypto/mls/session/
e2e_identity.rs

1use openmls::{
2    prelude::{Credential, Node, group_info::VerifiableGroupInfo},
3    treesync::RatchetTree,
4};
5use openmls_traits::OpenMlsCryptoProvider as _;
6use wire_e2e_identity::WireIdentityReader as _;
7
8use super::{Result, Session};
9use crate::{
10    CredentialType, E2eiConversationState, OpenMlsError, mls::credential::ext::CredentialExt as _,
11    mls_provider::AuthenticationService,
12};
13
14impl Session {
15    /// Verifies a Group state before joining it
16    pub async fn e2ei_verify_group_state(&self, group_info: VerifiableGroupInfo) -> Result<E2eiConversationState> {
17        let is_sender = true; // verify the ratchet tree as sender to turn on hardened verification
18        let Ok(rt) = group_info.take_ratchet_tree(&self.crypto_provider, is_sender).await else {
19            return Ok(E2eiConversationState::NotVerified);
20        };
21
22        let credentials = rt.iter().filter_map(|n| match n {
23            Some(Node::LeafNode(ln)) => Some(ln.credential()),
24            _ => None,
25        });
26
27        Ok(Self::compute_conversation_state(
28            credentials,
29            CredentialType::X509,
30            self.crypto_provider.authentication_service(),
31        )
32        .await)
33    }
34
35    /// Gets the e2ei conversation state from a `GroupInfo`. Useful to check if the group has e2ei
36    /// turned on or not before joining it.
37    pub async fn get_credential_in_use(
38        &self,
39        group_info: VerifiableGroupInfo,
40        credential_type: CredentialType,
41    ) -> Result<E2eiConversationState> {
42        // Not verifying the supplied the GroupInfo here could let attackers lure the clients about
43        // the e2ei state of a conversation and as a consequence degrade this conversation for all
44        // participants once joining it.
45        // This 👇 verifies the GroupInfo and the RatchetTree btw
46        let rt = group_info
47            .take_ratchet_tree(&self.crypto_provider, false)
48            .await
49            .map_err(OpenMlsError::wrap("taking ratchet tree"))?;
50        Self::get_credential_in_use_in_ratchet_tree(rt, credential_type, self.crypto_provider.authentication_service())
51            .await
52    }
53    pub(crate) async fn get_credential_in_use_in_ratchet_tree(
54        ratchet_tree: RatchetTree,
55        credential_type: CredentialType,
56        auth_service: &AuthenticationService,
57    ) -> Result<E2eiConversationState> {
58        let credentials = ratchet_tree.iter().filter_map(|n| match n {
59            Some(Node::LeafNode(ln)) => Some(ln.credential()),
60            _ => None,
61        });
62        Ok(Self::compute_conversation_state(credentials, credential_type, auth_service).await)
63    }
64
65    /// _credential_type will be used in the future to get the usage of VC Credentials, even Basics one.
66    /// Right now though, we do not need anything other than X509 so let's keep things simple.
67    pub(crate) async fn compute_conversation_state<'a>(
68        credentials: impl Iterator<Item = &'a Credential>,
69        _credential_type: CredentialType,
70        auth_service: &AuthenticationService,
71    ) -> E2eiConversationState {
72        let pki_env = auth_service.pki_env().await;
73        let Some(env) = pki_env else {
74            return E2eiConversationState::NotEnabled;
75        };
76
77        let mut is_e2ei = false;
78        let mut state = E2eiConversationState::Verified;
79
80        for credential in credentials {
81            let Ok(Some(cert)) = credential.parse_leaf_cert() else {
82                state = E2eiConversationState::NotVerified;
83                if is_e2ei {
84                    break;
85                }
86                continue;
87            };
88
89            is_e2ei = true;
90
91            let invalid_identity = cert.extract_identity(&env).await.is_err();
92
93            use openmls_x509_credential::X509Ext as _;
94            let is_time_valid = cert.is_time_valid().unwrap_or(false);
95            let is_time_invalid = !is_time_valid;
96            let is_revoked_or_invalid = env.validate_cert(&cert).await.is_err();
97            let is_invalid = invalid_identity || is_time_invalid || is_revoked_or_invalid;
98            if is_invalid {
99                state = E2eiConversationState::NotVerified;
100                break;
101            }
102        }
103
104        if is_e2ei {
105            state
106        } else {
107            E2eiConversationState::NotEnabled
108        }
109    }
110}