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, HashSet};
11
12use crate::{
13    RecursiveError,
14    mls::credential::{crl::get_new_crl_distribution_points, x509::CertificatePrivateKey},
15    prelude::{CertificateBundle, ClientId, ClientIdentifier, E2eiEnrollment, MlsCiphersuite},
16};
17use openmls_traits::OpenMlsCryptoProvider as _;
18use wire_e2e_identity::prelude::x509::extract_crl_uris;
19
20use super::TransactionContext;
21pub use crate::e2e_identity::E2eiDumpedPkiEnv;
22use crate::e2e_identity::NewCrlDistributionPoints;
23pub use error::{Error, Result};
24
25impl TransactionContext {
26    /// Creates an enrollment instance with private key material you can use in order to fetch
27    /// a new x509 certificate from the acme server.
28    ///
29    /// # Parameters
30    /// * `client_id` - client identifier e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:6add501bacd1d90e@example.com`
31    /// * `display_name` - human readable name displayed in the application e.g. `Smith, Alice M (QA)`
32    /// * `handle` - user handle e.g. `alice.smith.qa@example.com`
33    /// * `expiry_sec` - generated x509 certificate expiry in seconds
34    pub async fn e2ei_new_enrollment(
35        &self,
36        client_id: ClientId,
37        display_name: String,
38        handle: String,
39        team: Option<String>,
40        expiry_sec: u32,
41        ciphersuite: MlsCiphersuite,
42    ) -> Result<E2eiEnrollment> {
43        let signature_keypair = None; // fresh install without a Basic client. Supplying None will generate a new keypair
44        E2eiEnrollment::try_new(
45            client_id,
46            display_name,
47            handle,
48            team,
49            expiry_sec,
50            &self
51                .mls_provider()
52                .await
53                .map_err(RecursiveError::transaction("getting mls provider"))?,
54            ciphersuite,
55            signature_keypair,
56            #[cfg(not(target_family = "wasm"))]
57            None, // fresh install so no refresh token registered yet
58        )
59        .map_err(RecursiveError::e2e_identity("creating new enrollment"))
60        .map_err(Into::into)
61    }
62
63    /// Parses the ACME server response from the endpoint fetching x509 certificates and uses it
64    /// to initialize the MLS client with a certificate
65    pub async fn e2ei_mls_init_only(
66        &self,
67        enrollment: &mut E2eiEnrollment,
68        certificate_chain: String,
69        nb_init_key_packages: Option<usize>,
70    ) -> Result<NewCrlDistributionPoints> {
71        let sk = enrollment
72            .get_sign_key_for_mls()
73            .map_err(RecursiveError::e2e_identity("creating new enrollment"))?;
74        let cs = *enrollment.ciphersuite();
75        let certificate_chain = enrollment
76            .certificate_response(
77                certificate_chain,
78                self.mls_provider()
79                    .await
80                    .map_err(RecursiveError::transaction("getting mls provider"))?
81                    .authentication_service()
82                    .borrow()
83                    .await
84                    .as_ref()
85                    .ok_or(Error::PkiEnvironmentUnset)?,
86            )
87            .await
88            .map_err(RecursiveError::e2e_identity("getting certificate response"))?;
89
90        let crl_new_distribution_points = self.extract_dp_on_init(&certificate_chain[..]).await?;
91
92        let private_key = CertificatePrivateKey {
93            value: sk,
94            signature_scheme: cs.signature_algorithm(),
95        };
96
97        let cert_bundle = CertificateBundle {
98            certificate_chain,
99            private_key,
100        };
101        let identifier = ClientIdentifier::X509(HashMap::from([(cs.signature_algorithm(), cert_bundle)]));
102        self.mls_init(identifier, vec![cs], nb_init_key_packages)
103            .await
104            .map_err(RecursiveError::transaction("initializing mls"))?;
105        Ok(crl_new_distribution_points)
106    }
107
108    /// When x509 new credentials are registered this extracts the new CRL Distribution Point from the end entity certificate
109    /// and all the intermediates
110    async fn extract_dp_on_init(&self, certificate_chain: &[Vec<u8>]) -> Result<NewCrlDistributionPoints> {
111        use x509_cert::der::Decode as _;
112
113        // Own intermediates are not provided by smallstep in the /federation endpoint so we got to intercept them here, at issuance
114        let size = certificate_chain.len();
115        let mut crl_new_distribution_points = HashSet::new();
116        if size > 1 {
117            for int in certificate_chain.iter().skip(1).rev() {
118                let mut crl_dp = self
119                    .e2ei_register_intermediate_ca_der(int)
120                    .await
121                    .map_err(RecursiveError::transaction("registering intermediate ca der"))?;
122                if let Some(crl_dp) = crl_dp.take() {
123                    crl_new_distribution_points.extend(crl_dp);
124                }
125            }
126        }
127
128        let ee = certificate_chain.first().ok_or(Error::InvalidCertificateChain)?;
129        let ee = x509_cert::Certificate::from_der(ee)
130            .map_err(crate::mls::credential::Error::DecodeX509)
131            .map_err(RecursiveError::mls_credential("decoding x509 credential"))?;
132        let mut ee_crl_dp = extract_crl_uris(&ee).map_err(RecursiveError::e2e_identity("extracting crl urls"))?;
133        if let Some(crl_dp) = ee_crl_dp.take() {
134            crl_new_distribution_points.extend(crl_dp);
135        }
136
137        get_new_crl_distribution_points(
138            &self
139                .mls_provider()
140                .await
141                .map_err(RecursiveError::transaction("getting mls provider"))?,
142            crl_new_distribution_points,
143        )
144        .await
145        .map_err(RecursiveError::mls_credential("getting new crl distribution points"))
146        .map_err(Into::into)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use crate::e2e_identity::enrollment::test_utils as e2ei_utils;
153    use crate::mls::conversation::Conversation as _;
154    use crate::test_utils::x509::X509TestChain;
155    use crate::{prelude::*, test_utils::*};
156    use wasm_bindgen_test::*;
157
158    wasm_bindgen_test_configure!(run_in_browser);
159
160    #[apply(all_cred_cipher)]
161    #[wasm_bindgen_test]
162    async fn e2e_identity_should_work(case: TestContext) {
163        use e2ei_utils::E2EI_CLIENT_ID_URI;
164
165        let mut cc = SessionContext::new_uninitialized(&case).await;
166        Box::pin(async move {
167            let x509_test_chain = X509TestChain::init_empty(case.signature_scheme());
168
169            let is_renewal = false;
170
171            let (mut enrollment, cert) = e2ei_utils::e2ei_enrollment(
172                &mut cc,
173                &case,
174                &x509_test_chain,
175                Some(E2EI_CLIENT_ID_URI),
176                is_renewal,
177                e2ei_utils::init_enrollment,
178                e2ei_utils::noop_restore,
179            )
180            .await
181            .unwrap();
182
183            cc.transaction
184                .e2ei_mls_init_only(&mut enrollment, cert, Some(INITIAL_KEYING_MATERIAL_COUNT))
185                .await
186                .unwrap();
187
188            // verify the created client can create a conversation
189            let id = conversation_id();
190            cc.transaction
191                .new_conversation(&id, MlsCredentialType::X509, case.cfg.clone())
192                .await
193                .unwrap();
194            cc.transaction
195                .conversation(&id)
196                .await
197                .unwrap()
198                .encrypt_message("Hello e2e identity !")
199                .await
200                .unwrap();
201            assert_eq!(
202                cc.transaction
203                    .conversation(&id)
204                    .await
205                    .unwrap()
206                    .e2ei_conversation_state()
207                    .await
208                    .unwrap(),
209                E2eiConversationState::Verified
210            );
211            assert!(cc.transaction.e2ei_is_enabled(case.signature_scheme()).await.unwrap());
212        })
213        .await
214    }
215}