core_crypto/transaction_context/e2e_identity/
mod.rs

1//! This module contains all behaviour of a transaction context connected to end-to-end identity.
2
3pub(crate) mod conversation_state;
4pub mod enabled;
5mod error;
6mod init_certificates;
7mod rotate;
8mod stash;
9
10use std::collections::HashMap;
11
12use crate::{
13    RecursiveError,
14    mls::credential::x509::CertificatePrivateKey,
15    prelude::{CertificateBundle, ClientId, ClientIdentifier, E2eiEnrollment, MlsCiphersuite},
16};
17use openmls_traits::OpenMlsCryptoProvider as _;
18
19use super::TransactionContext;
20pub use crate::e2e_identity::E2eiDumpedPkiEnv;
21use crate::e2e_identity::NewCrlDistributionPoints;
22pub use error::{Error, Result};
23
24impl TransactionContext {
25    /// Creates an enrollment instance with private key material you can use in order to fetch
26    /// a new x509 certificate from the acme server.
27    ///
28    /// # Parameters
29    /// * `client_id` - client identifier e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:6add501bacd1d90e@example.com`
30    /// * `display_name` - human readable name displayed in the application e.g. `Smith, Alice M (QA)`
31    /// * `handle` - user handle e.g. `alice.smith.qa@example.com`
32    /// * `expiry_sec` - generated x509 certificate expiry in seconds
33    pub async fn e2ei_new_enrollment(
34        &self,
35        client_id: ClientId,
36        display_name: String,
37        handle: String,
38        team: Option<String>,
39        expiry_sec: u32,
40        ciphersuite: MlsCiphersuite,
41    ) -> Result<E2eiEnrollment> {
42        let signature_keypair = None; // fresh install without a Basic client. Supplying None will generate a new keypair
43        E2eiEnrollment::try_new(
44            client_id,
45            display_name,
46            handle,
47            team,
48            expiry_sec,
49            &self
50                .mls_provider()
51                .await
52                .map_err(RecursiveError::transaction("getting mls provider"))?,
53            ciphersuite,
54            signature_keypair,
55            #[cfg(not(target_family = "wasm"))]
56            None, // fresh install so no refresh token registered yet
57        )
58        .map_err(RecursiveError::e2e_identity("creating new enrollment"))
59        .map_err(Into::into)
60    }
61
62    /// Parses the ACME server response from the endpoint fetching x509 certificates and uses it
63    /// to initialize the MLS client with a certificate
64    pub async fn e2ei_mls_init_only(
65        &self,
66        enrollment: &mut E2eiEnrollment,
67        certificate_chain: String,
68        nb_init_key_packages: Option<usize>,
69    ) -> Result<NewCrlDistributionPoints> {
70        let sk = enrollment
71            .get_sign_key_for_mls()
72            .map_err(RecursiveError::e2e_identity("creating new enrollment"))?;
73        let cs = *enrollment.ciphersuite();
74        let certificate_chain = enrollment
75            .certificate_response(
76                certificate_chain,
77                self.mls_provider()
78                    .await
79                    .map_err(RecursiveError::transaction("getting mls provider"))?
80                    .authentication_service()
81                    .borrow()
82                    .await
83                    .as_ref()
84                    .ok_or(Error::PkiEnvironmentUnset)?,
85            )
86            .await
87            .map_err(RecursiveError::e2e_identity("getting certificate response"))?;
88
89        let crl_new_distribution_points = self
90            .extract_dp_on_init(&certificate_chain[..])
91            .await
92            .map_err(RecursiveError::mls_credential("extracting dp on init"))?;
93
94        let private_key = CertificatePrivateKey {
95            value: sk,
96            signature_scheme: cs.signature_algorithm(),
97        };
98
99        let cert_bundle = CertificateBundle {
100            certificate_chain,
101            private_key,
102        };
103        let identifier = ClientIdentifier::X509(HashMap::from([(cs.signature_algorithm(), cert_bundle)]));
104        self.mls_init(identifier, vec![cs], nb_init_key_packages)
105            .await
106            .map_err(RecursiveError::mls("initializing mls"))?;
107        Ok(crl_new_distribution_points)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use crate::e2e_identity::enrollment::test_utils as e2ei_utils;
114    use crate::mls::conversation::Conversation as _;
115    use crate::test_utils::x509::X509TestChain;
116    use crate::{prelude::*, test_utils::*};
117    use wasm_bindgen_test::*;
118
119    wasm_bindgen_test_configure!(run_in_browser);
120
121    #[apply(all_cred_cipher)]
122    #[wasm_bindgen_test]
123    async fn e2e_identity_should_work(case: TestCase) {
124        use e2ei_utils::E2EI_CLIENT_ID_URI;
125
126        run_test_wo_clients(case.clone(), move |mut cc| {
127            Box::pin(async move {
128                let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
129
130                let is_renewal = false;
131
132                let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
133                    &mut cc,
134                    &case,
135                    &x509_test_chain,
136                    Some(E2EI_CLIENT_ID_URI),
137                    is_renewal,
138                    e2ei_utils::init_enrollment,
139                    e2ei_utils::noop_restore,
140                )
141                .await
142                .unwrap();
143
144                cc.context
145                    .e2ei_mls_init_only(&mut enrollment, cert, Some(INITIAL_KEYING_MATERIAL_COUNT))
146                    .await
147                    .unwrap();
148
149                // verify the created client can create a conversation
150                let id = conversation_id();
151                cc.context
152                    .new_conversation(&id, MlsCredentialType::X509, case.cfg.clone())
153                    .await
154                    .unwrap();
155                cc.context
156                    .conversation(&id)
157                    .await
158                    .unwrap()
159                    .encrypt_message("Hello e2e identity !")
160                    .await
161                    .unwrap();
162                assert_eq!(
163                    cc.context
164                        .conversation(&id)
165                        .await
166                        .unwrap()
167                        .e2ei_conversation_state()
168                        .await
169                        .unwrap(),
170                    E2eiConversationState::Verified
171                );
172                assert!(cc.context.e2ei_is_enabled(case.signature_scheme()).await.unwrap());
173            })
174        })
175        .await
176    }
177}