core_crypto/mls/session/
e2e_identity.rs1use core_crypto_keystore::traits::FetchFromDatabase;
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, CredentialType, E2eiConversationState, MlsError,
12 mls::{credential::ext::CredentialExt as _, session::Error},
13};
14
15impl<D> Session<D> {
16 pub async fn e2ei_is_pki_env_setup(&self) -> bool {
18 self.crypto_provider.is_pki_env_setup().await
19 }
20
21 pub async fn e2ei_is_enabled(&self, ciphersuite: Ciphersuite) -> Result<bool>
28 where
29 D: FetchFromDatabase,
30 {
31 let credentials = self
32 .find_credentials(CredentialFindFilters::builder().ciphersuite(ciphersuite).build())
33 .await?;
34
35 let x509_credential_exists = credentials
36 .iter()
37 .any(|credential| credential.r#type() == CredentialType::X509);
38 if x509_credential_exists {
39 return Ok(true);
40 }
41
42 if !credentials.is_empty() {
43 return Ok(false);
44 }
45
46 Err(Error::NoCredentialWithType(CredentialType::Basic))
47 }
48
49 pub async fn e2ei_verify_group_state(&self, group_info: VerifiableGroupInfo) -> Result<E2eiConversationState> {
51 self.crypto_provider
52 .authentication_service()
53 .refresh_time_of_interest()
54 .await;
55
56 let cs = group_info.ciphersuite().into();
57
58 let is_sender = true; let Ok(rt) = group_info.take_ratchet_tree(&self.crypto_provider, is_sender).await else {
60 return Ok(E2eiConversationState::NotVerified);
61 };
62
63 let credentials = rt.iter().filter_map(|n| match n {
64 Some(Node::LeafNode(ln)) => Some(ln.credential()),
65 _ => None,
66 });
67
68 Ok(Self::compute_conversation_state(
69 cs,
70 credentials,
71 CredentialType::X509,
72 self.crypto_provider.authentication_service().borrow().await.as_ref(),
73 )
74 .await)
75 }
76
77 pub async fn get_credential_in_use(
80 &self,
81 group_info: VerifiableGroupInfo,
82 credential_type: CredentialType,
83 ) -> Result<E2eiConversationState> {
84 let cs = group_info.ciphersuite().into();
85 let rt = group_info
90 .take_ratchet_tree(&self.crypto_provider, false)
91 .await
92 .map_err(MlsError::wrap("taking ratchet tree"))?;
93 Self::get_credential_in_use_in_ratchet_tree(
94 cs,
95 rt,
96 credential_type,
97 self.crypto_provider.authentication_service().borrow().await.as_ref(),
98 )
99 .await
100 }
101 pub(crate) async fn get_credential_in_use_in_ratchet_tree(
102 ciphersuite: Ciphersuite,
103 ratchet_tree: RatchetTree,
104 credential_type: CredentialType,
105 env: Option<&wire_e2e_identity::x509_check::revocation::PkiEnvironment>,
106 ) -> Result<E2eiConversationState> {
107 let credentials = ratchet_tree.iter().filter_map(|n| match n {
108 Some(Node::LeafNode(ln)) => Some(ln.credential()),
109 _ => None,
110 });
111 Ok(Self::compute_conversation_state(ciphersuite, credentials, credential_type, env).await)
112 }
113
114 pub(crate) async fn compute_conversation_state<'a>(
117 ciphersuite: Ciphersuite,
118 credentials: impl Iterator<Item = &'a Credential>,
119 _credential_type: CredentialType,
120 env: Option<&wire_e2e_identity::x509_check::revocation::PkiEnvironment>,
121 ) -> E2eiConversationState {
122 let mut is_e2ei = false;
123 let mut state = E2eiConversationState::Verified;
124
125 for credential in credentials {
126 let Ok(Some(cert)) = credential.parse_leaf_cert() else {
127 state = E2eiConversationState::NotVerified;
128 if is_e2ei {
129 break;
130 }
131 continue;
132 };
133
134 is_e2ei = true;
135
136 let invalid_identity = cert.extract_identity(env, ciphersuite.e2ei_hash_alg()).is_err();
137
138 use openmls_x509_credential::X509Ext as _;
139 let is_time_valid = cert.is_time_valid().unwrap_or(false);
140 let is_time_invalid = !is_time_valid;
141 let is_revoked_or_invalid = env
142 .map(|e| e.validate_cert_and_revocation(&cert).is_err())
143 .unwrap_or(false);
144
145 let is_invalid = invalid_identity || is_time_invalid || is_revoked_or_invalid;
146 if is_invalid {
147 state = E2eiConversationState::NotVerified;
148 break;
149 }
150 }
151
152 if is_e2ei {
153 state
154 } else {
155 E2eiConversationState::NotEnabled
156 }
157 }
158}