core_crypto/mls/session/
e2e_identity.rs1use futures_util::TryFutureExt;
2use openmls::{
3 prelude::{Credential, Node, group_info::VerifiableGroupInfo},
4 treesync::RatchetTree,
5};
6use openmls_traits::OpenMlsCryptoProvider as _;
7use wire_e2e_identity::WireIdentityReader as _;
8
9use super::{Result, Session};
10use crate::{
11 CipherSuite, CredentialFindFilters, CredentialRef, CredentialType, E2eiConversationState, OpenMlsError,
12 RecursiveError,
13 mls::{credential::ext::CredentialExt as _, session::Error},
14 mls_provider::AuthenticationService,
15};
16
17impl Session {
18 pub async fn e2ei_is_pki_env_setup(&self) -> bool {
20 self.crypto_provider.is_pki_env_setup().await
21 }
22
23 pub async fn e2ei_is_enabled(&self, cipher_suite: CipherSuite) -> Result<bool> {
30 let credentials = CredentialRef::find(
31 &self.database,
32 CredentialFindFilters::builder().cipher_suite(cipher_suite).build(),
33 )
34 .map_err(RecursiveError::mls_credential_ref("finding credentials with filters"))
35 .await?;
36
37 let x509_credential_exists = credentials
38 .iter()
39 .any(|credential| credential.r#type() == CredentialType::X509);
40 if x509_credential_exists {
41 return Ok(true);
42 }
43
44 if !credentials.is_empty() {
45 return Ok(false);
46 }
47
48 Err(Error::NoCredentialWithType(CredentialType::Basic))
49 }
50
51 pub async fn e2ei_verify_group_state(&self, group_info: VerifiableGroupInfo) -> Result<E2eiConversationState> {
53 let cs = group_info.ciphersuite().into();
54
55 let is_sender = true; let Ok(rt) = group_info.take_ratchet_tree(&self.crypto_provider, is_sender).await else {
57 return Ok(E2eiConversationState::NotVerified);
58 };
59
60 let credentials = rt.iter().filter_map(|n| match n {
61 Some(Node::LeafNode(ln)) => Some(ln.credential()),
62 _ => None,
63 });
64
65 Ok(Self::compute_conversation_state(
66 cs,
67 credentials,
68 CredentialType::X509,
69 self.crypto_provider.authentication_service(),
70 )
71 .await)
72 }
73
74 pub async fn get_credential_in_use(
77 &self,
78 group_info: VerifiableGroupInfo,
79 credential_type: CredentialType,
80 ) -> Result<E2eiConversationState> {
81 let cs = group_info.ciphersuite().into();
82 let rt = group_info
87 .take_ratchet_tree(&self.crypto_provider, false)
88 .await
89 .map_err(OpenMlsError::wrap("taking ratchet tree"))?;
90 Self::get_credential_in_use_in_ratchet_tree(
91 cs,
92 rt,
93 credential_type,
94 self.crypto_provider.authentication_service(),
95 )
96 .await
97 }
98 pub(crate) async fn get_credential_in_use_in_ratchet_tree(
99 cipher_suite: CipherSuite,
100 ratchet_tree: RatchetTree,
101 credential_type: CredentialType,
102 auth_service: &AuthenticationService,
103 ) -> Result<E2eiConversationState> {
104 let credentials = ratchet_tree.iter().filter_map(|n| match n {
105 Some(Node::LeafNode(ln)) => Some(ln.credential()),
106 _ => None,
107 });
108 Ok(Self::compute_conversation_state(cipher_suite, credentials, credential_type, auth_service).await)
109 }
110
111 pub(crate) async fn compute_conversation_state<'a>(
114 cipher_suite: CipherSuite,
115 credentials: impl Iterator<Item = &'a Credential>,
116 _credential_type: CredentialType,
117 auth_service: &AuthenticationService,
118 ) -> E2eiConversationState {
119 let pki_env = auth_service.pki_env().await;
120 let Some(env) = pki_env else {
121 return E2eiConversationState::NotEnabled;
122 };
123
124 let mut is_e2ei = false;
125 let mut state = E2eiConversationState::Verified;
126
127 for credential in credentials {
128 let Ok(Some(cert)) = credential.parse_leaf_cert() else {
129 state = E2eiConversationState::NotVerified;
130 if is_e2ei {
131 break;
132 }
133 continue;
134 };
135
136 is_e2ei = true;
137
138 let invalid_identity = cert.extract_identity(&env, cipher_suite.e2ei_hash_alg()).await.is_err();
139
140 use openmls_x509_credential::X509Ext as _;
141 let is_time_valid = cert.is_time_valid().unwrap_or(false);
142 let is_time_invalid = !is_time_valid;
143 let is_revoked_or_invalid = env.validate_cert(&cert).await.is_err();
144 let is_invalid = invalid_identity || is_time_invalid || is_revoked_or_invalid;
145 if is_invalid {
146 state = E2eiConversationState::NotVerified;
147 break;
148 }
149 }
150
151 if is_e2ei {
152 state
153 } else {
154 E2eiConversationState::NotEnabled
155 }
156 }
157}