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        run_test_wo_clients(case.clone(), move |mut cc| {
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        })
214        .await
215    }
216}